Commit 0eb4fd2f authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 78d8830c
<script>
import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { mapState } from 'vuex';
import { COPY_LOGIN_TITLE, COPY_BUILD_TITLE, COPY_PUSH_TITLE, QUICK_START } from '../constants';
export default {
name: 'ProjectEmptyState',
......@@ -11,20 +13,24 @@ export default {
GlSprintf,
GlLink,
},
i18n: {
quickStart: QUICK_START,
copyLoginTitle: COPY_LOGIN_TITLE,
copyBuildTitle: COPY_BUILD_TITLE,
copyPushTitle: COPY_PUSH_TITLE,
introText: s__(
`ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}`,
),
notLoggedInMessage: s__(
`ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password.`,
),
addImageText: s__(
'ContainerRegistry|You can add an image to this registry with the following commands:',
),
},
computed: {
...mapState(['config']),
dockerBuildCommand() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `docker build -t ${this.config.repositoryUrl} .`;
},
dockerPushCommand() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `docker push ${this.config.repositoryUrl}`;
},
dockerLoginCommand() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `docker login ${this.config.registryHostUrlWithPort}`;
},
...mapGetters(['dockerBuildCommand', 'dockerPushCommand', 'dockerLoginCommand']),
},
};
</script>
......@@ -36,28 +42,15 @@ export default {
>
<template #description>
<p class="js-no-container-images-text">
<gl-sprintf
:message="
s__(`ContainerRegistry|With the Container Registry, every project can have its own space to
store its Docker images. %{docLinkStart}More Information%{docLinkEnd}`)
"
>
<gl-sprintf :message="$options.i18n.introText">
<template #docLink="{content}">
<gl-link :href="config.helpPagePath" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<h5>{{ s__('ContainerRegistry|Quick Start') }}</h5>
<h5>{{ $options.i18n.quickStart }}</h5>
<p class="js-not-logged-in-to-registry-text">
<gl-sprintf
:message="
s__(`ContainerRegistry|If you are not already logged in, you need to authenticate to
the Container Registry by using your GitLab username and password. If you have
%{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a
%{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd}
instead of a password.`)
"
>
<gl-sprintf :message="$options.i18n.notLoggedInMessage">
<template #twofaDocLink="{content}">
<gl-link :href="config.twoFactorAuthHelpLink" target="_blank">{{ content }}</gl-link>
</template>
......@@ -73,18 +66,14 @@ export default {
<span class="input-group-append">
<clipboard-button
:text="dockerLoginCommand"
:title="s__('ContainerRegistry|Copy login command')"
:title="$options.i18n.copyLoginTitle"
class="input-group-text"
/>
</span>
</div>
<p></p>
<p>
{{
s__(
'ContainerRegistry|You can add an image to this registry with the following commands:',
)
}}
{{ $options.i18n.addImageText }}
</p>
<div class="input-group append-bottom-10">
......@@ -92,7 +81,7 @@ export default {
<span class="input-group-append">
<clipboard-button
:text="dockerBuildCommand"
:title="s__('ContainerRegistry|Copy build command')"
:title="$options.i18n.copyBuildTitle"
class="input-group-text"
/>
</span>
......@@ -103,7 +92,7 @@ export default {
<span class="input-group-append">
<clipboard-button
:text="dockerPushCommand"
:title="s__('ContainerRegistry|Copy push command')"
:title="$options.i18n.copyPushTitle"
class="input-group-text"
/>
</span>
......
<script>
import { GlDropdown, GlFormGroup, GlFormInputGroup } from '@gitlab/ui';
import { mapGetters } from 'vuex';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import {
QUICK_START,
LOGIN_COMMAND_LABEL,
COPY_LOGIN_TITLE,
BUILD_COMMAND_LABEL,
COPY_BUILD_TITLE,
PUSH_COMMAND_LABEL,
COPY_PUSH_TITLE,
} from '../constants';
export default {
components: {
GlDropdown,
GlFormGroup,
GlFormInputGroup,
ClipboardButton,
},
i18n: {
dropdownTitle: QUICK_START,
loginCommandLabel: LOGIN_COMMAND_LABEL,
copyLoginTitle: COPY_LOGIN_TITLE,
buildCommandLabel: BUILD_COMMAND_LABEL,
copyBuildTitle: COPY_BUILD_TITLE,
pushCommandLabel: PUSH_COMMAND_LABEL,
copyPushTitle: COPY_PUSH_TITLE,
},
computed: {
...mapGetters(['dockerBuildCommand', 'dockerPushCommand', 'dockerLoginCommand']),
},
};
</script>
<template>
<gl-dropdown :text="$options.i18n.dropdownTitle" variant="primary" size="sm" right>
<!-- This li is used as a container since gl-dropdown produces a root ul, this mimics the functionality exposed by b-dropdown-form -->
<li role="presentation" class="px-2 py-1 dropdown-menu-large">
<form>
<gl-form-group
label-size="sm"
label-for="docker-login-btn"
:label="$options.i18n.loginCommandLabel"
>
<gl-form-input-group id="docker-login-btn" :value="dockerLoginCommand" readonly>
<template #append>
<clipboard-button
class="border"
:text="dockerLoginCommand"
:title="$options.i18n.copyLoginTitle"
/>
</template>
</gl-form-input-group>
</gl-form-group>
<gl-form-group
label-size="sm"
label-for="docker-build-btn"
:label="$options.i18n.buildCommandLabel"
>
<gl-form-input-group id="docker-build-btn" :value="dockerBuildCommand" readonly>
<template #append>
<clipboard-button
class="border"
:text="dockerBuildCommand"
:title="$options.i18n.copyBuildTitle"
/>
</template>
</gl-form-input-group>
</gl-form-group>
<gl-form-group
class="mb-0"
label-size="sm"
label-for="docker-push-btn"
:label="$options.i18n.pushCommandLabel"
>
<gl-form-input-group id="docker-push-btn" :value="dockerPushCommand" readonly>
<template #append>
<clipboard-button
class="border"
:text="dockerPushCommand"
:title="$options.i18n.copyPushTitle"
/>
</template>
</gl-form-input-group>
</gl-form-group>
</form>
</li>
</gl-dropdown>
</template>
......@@ -47,3 +47,11 @@ export const EXPIRATION_POLICY_ALERT_FULL_MESSAGE = s__(
export const EXPIRATION_POLICY_ALERT_SHORT_MESSAGE = s__(
'ContainerRegistry|The retention and expiration policy for this Container Registry has been enabled. For more information visit the %{linkStart}documentation%{linkEnd}',
);
export const QUICK_START = s__('ContainerRegistry|Quick Start');
export const LOGIN_COMMAND_LABEL = s__('ContainerRegistry|Login');
export const COPY_LOGIN_TITLE = s__('ContainerRegistry|Copy login command');
export const BUILD_COMMAND_LABEL = s__('ContainerRegistry|Build an image');
export const COPY_BUILD_TITLE = s__('ContainerRegistry|Copy build command');
export const PUSH_COMMAND_LABEL = s__('ContainerRegistry|Push an image');
export const COPY_PUSH_TITLE = s__('ContainerRegistry|Copy push command');
......@@ -16,6 +16,7 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ProjectEmptyState from '../components/project_empty_state.vue';
import GroupEmptyState from '../components/group_empty_state.vue';
import ProjectPolicyAlert from '../components/project_policy_alert.vue';
import QuickstartDropdown from '../components/quickstart_dropdown.vue';
export default {
name: 'RegistryListApp',
......@@ -26,6 +27,7 @@ export default {
GroupEmptyState,
ProjectPolicyAlert,
ClipboardButton,
QuickstartDropdown,
GlButton,
GlIcon,
GlModal,
......@@ -62,6 +64,9 @@ export default {
this.requestImagesList({ page });
},
},
showQuickStartDropdown() {
return Boolean(!this.isLoading && !this.config?.isGroupPage && this.images?.length);
},
},
methods: {
...mapActions(['requestImagesList', 'requestDeleteImage']),
......@@ -114,7 +119,10 @@ export default {
<template v-else>
<div>
<h4>{{ s__('ContainerRegistry|Container Registry') }}</h4>
<div class="d-flex justify-content-between align-items-center">
<h4>{{ s__('ContainerRegistry|Container Registry') }}</h4>
<quickstart-dropdown v-if="showQuickStartDropdown" class="d-none d-sm-block" />
</div>
<p>
<gl-sprintf
:message="
......
// eslint-disable-next-line import/prefer-default-export
export const tags = state => {
// to show the loader inside the table we need to pass an empty array to gl-table whenever the table is loading
// this is to take in account isLoading = true and state.tags =[1,2,3] during pagination and delete
return state.isLoading ? [] : state.tags;
};
export const dockerBuildCommand = state => {
/* eslint-disable @gitlab/require-i18n-strings */
return `docker build -t ${state.config.repositoryUrl} .`;
};
export const dockerPushCommand = state => {
/* eslint-disable @gitlab/require-i18n-strings */
return `docker push ${state.config.repositoryUrl}`;
};
export const dockerLoginCommand = state => {
/* eslint-disable @gitlab/require-i18n-strings */
return `docker login ${state.config.registryHostUrlWithPort}`;
};
......@@ -7,7 +7,7 @@ module Groups
before_action :authorize_admin_group!
before_action :authorize_update_max_artifacts_size!, only: [:update]
before_action do
push_frontend_feature_flag(:new_variables_ui, @group, default_enabled: true)
push_frontend_feature_flag(:new_variables_ui, @group)
end
before_action :define_variables, only: [:show, :create_deploy_token]
......
......@@ -6,7 +6,7 @@ module Projects
before_action :authorize_admin_pipeline!
before_action :define_variables
before_action do
push_frontend_feature_flag(:new_variables_ui, @project, default_enabled: true)
push_frontend_feature_flag(:new_variables_ui, @project)
end
def show
......
......@@ -78,14 +78,15 @@
.card-footer
= paginate @projects, param_name: 'projects_page', theme: 'gitlab'
- if @group.shared_projects.any?
- shared_projects = @group.shared_projects.sort_by(&:name)
- unless shared_projects.empty?
.card
.card-header
= _('Projects shared with %{group_name}') % { group_name: @group.name }
%span.badge.badge-pill
#{@group.shared_projects.count}
#{shared_projects.size}
%ul.content-list
- @group.shared_projects.sort_by(&:name).each do |project|
- shared_projects.each do |project|
%li
%strong
= link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project]
......
......@@ -14,51 +14,52 @@
= _('Last activity')
.table-mobile-content
= user.last_activity_on.nil? ? _('Never') : l(user.last_activity_on, format: :admin)
.table-section.section-20.table-button-footer
.table-action-buttons
= link_to _('Edit'), edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn btn-default'
- unless user == current_user
%button.dropdown-new.btn.btn-default{ type: 'button', data: { toggle: 'dropdown' } }
= sprite_icon('settings')
= sprite_icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right
%li.dropdown-header
= _('Settings')
%li
- if user.ldap_blocked?
%span.small
= s_('AdminUsers|Cannot unblock LDAP blocked users')
- elsif user.blocked?
= link_to _('Unblock'), unblock_admin_user_path(user), method: :put
- else
%button.btn{ data: { 'gl-modal-action': 'block',
url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Block')
- if user.can_be_deactivated?
- unless user.internal?
.table-section.section-20.table-button-footer
.table-action-buttons
= link_to _('Edit'), edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn btn-default'
- unless user == current_user
%button.dropdown-new.btn.btn-default{ type: 'button', data: { toggle: 'dropdown' } }
= sprite_icon('settings')
= sprite_icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right
%li.dropdown-header
= _('Settings')
%li
%button.btn{ data: { 'gl-modal-action': 'deactivate',
url: deactivate_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Deactivate')
- elsif user.deactivated?
%li
= link_to _('Activate'), activate_admin_user_path(user), method: :put
- if user.access_locked?
%li
= link_to _('Unlock'), unlock_admin_user_path(user), method: :put, data: { confirm: _('Are you sure?') }
- if can?(current_user, :destroy_user, user)
%li.divider
- if user.can_be_removed?
%li
%button.delete-user-button.btn.text-danger{ data: { 'gl-modal-action': 'delete',
delete_user_url: admin_user_path(user),
block_user_url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Delete user')
%li
%button.delete-user-button.btn.text-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
delete_user_url: admin_user_path(user, hard_delete: true),
block_user_url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Delete user and contributions')
- if user.ldap_blocked?
%span.small
= s_('AdminUsers|Cannot unblock LDAP blocked users')
- elsif user.blocked?
= link_to _('Unblock'), unblock_admin_user_path(user), method: :put
- else
%button.btn{ data: { 'gl-modal-action': 'block',
url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Block')
- if user.can_be_deactivated?
%li
%button.btn{ data: { 'gl-modal-action': 'deactivate',
url: deactivate_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Deactivate')
- elsif user.deactivated?
%li
= link_to _('Activate'), activate_admin_user_path(user), method: :put
- if user.access_locked?
%li
= link_to _('Unlock'), unlock_admin_user_path(user), method: :put, data: { confirm: _('Are you sure?') }
- if can?(current_user, :destroy_user, user)
%li.divider
- if user.can_be_removed?
%li
%button.delete-user-button.btn.text-danger{ data: { 'gl-modal-action': 'delete',
delete_user_url: admin_user_path(user),
block_user_url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Delete user')
%li
%button.delete-user-button.btn.text-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
delete_user_url: admin_user_path(user, hard_delete: true),
block_user_url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Delete user and contributions')
......@@ -5,7 +5,7 @@
- link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/README', anchor: 'protected-variables') }
= s_('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
- if Feature.enabled?(:new_variables_ui, @project || @group, default_enabled: true)
- if Feature.enabled?(:new_variables_ui, @project || @group)
- is_group = !@group.nil?
#js-ci-project-variables{ data: { endpoint: save_endpoint, project_id: @project&.id || '', group: is_group.to_s, maskable_regex: ci_variable_maskable_regex} }
......
---
title: Hide admin user actions for ghost and bot users
merge_request: 27162
author:
type: fixed
---
title: New package list is enabled which includes filtering by type
merge_request: 18860
author:
type: added
---
title: Update UI for project and group settings CI variables
merge_request: 27411
author:
type: added
---
title: Add forking_access_level to projects API
merge_request: 27514
author: Mathieu Parent
type: added
---
title: Update UI for project and group settings CI variables
merge_request: 26901
author:
type: added
......@@ -23,9 +23,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/ap
Example response:
```json
[
{ issues_count : 10 }
]
{ "issues_count": 10 }
```
## Get count of recently created merge requests for group
......@@ -49,7 +47,5 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/ap
Example response:
```json
[
{ merge_requests_count : 10 }
]
{ "merge_requests_count": 10 }
```
......@@ -1012,6 +1012,7 @@ POST /projects
| `issues_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `repository_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `merge_requests_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `forking_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
......@@ -1080,6 +1081,7 @@ POST /projects/user/:user_id
| `issues_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `repository_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `merge_requests_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `forking_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
......@@ -1147,6 +1149,7 @@ PUT /projects/:id
| `issues_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `repository_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `merge_requests_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `forking_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
......
......@@ -202,7 +202,7 @@ so they don't have to be re-fetched from the public internet.
NOTE: **Note:**
For more examples, check out our [GitLab CI/CD
templates](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/lib/gitlab/ci/templates).
templates](https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates).
### Caching Node.js dependencies
......@@ -214,7 +214,7 @@ we tell npm to use `./.npm` instead, and it is cached per-branch:
```yaml
#
# https://gitlab.com/gitlab-org/gitlab-foss/tree/master/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml
#
image: node:latest
......@@ -241,7 +241,7 @@ are cached per-branch:
```yaml
#
# https://gitlab.com/gitlab-org/gitlab-foss/tree/master/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
#
image: php:7.2
......@@ -270,7 +270,7 @@ pip's cache is defined under `.cache/pip/` and both are cached per-branch:
```yaml
#
# https://gitlab.com/gitlab-org/gitlab-foss/tree/master/lib/gitlab/ci/templates/Python.gitlab-ci.yml
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/Python.gitlab-ci.yml
#
image: python:latest
......@@ -310,7 +310,7 @@ jobs inherit it. Gems are installed in `vendor/ruby/` and are cached per-branch:
```yaml
#
# https://gitlab.com/gitlab-org/gitlab-foss/tree/master/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
#
image: ruby:2.6
......
......@@ -45,7 +45,7 @@ In addition, pipelines for merged results have the following limitations:
see [#11934](https://gitlab.com/gitlab-org/gitlab/issues/11934).
- This feature is not available for
[fast forward merges](../../../user/project/merge_requests/fast_forward_merge.md) yet.
To follow progress, see [#58226](https://gitlab.com/gitlab-org/gitlab-foss/issues/58226).
To follow progress, see [#58226](https://gitlab.com/gitlab-org/gitlab/-/issues/26996).
## Enabling Pipelines for Merged Results
......
......@@ -59,6 +59,7 @@ module API
expose(:issues_access_level) { |project, options| project.project_feature.string_access_level(:issues) }
expose(:repository_access_level) { |project, options| project.project_feature.string_access_level(:repository) }
expose(:merge_requests_access_level) { |project, options| project.project_feature.string_access_level(:merge_requests) }
expose(:forking_access_level) { |project, options| project.project_feature.string_access_level(:forking) }
expose(:wiki_access_level) { |project, options| project.project_feature.string_access_level(:wiki) }
expose(:builds_access_level) { |project, options| project.project_feature.string_access_level(:builds) }
expose(:snippets_access_level) { |project, options| project.project_feature.string_access_level(:snippets) }
......
......@@ -24,6 +24,7 @@ module API
optional :issues_access_level, type: String, values: %w(disabled private enabled), desc: 'Issues access level. One of `disabled`, `private` or `enabled`'
optional :repository_access_level, type: String, values: %w(disabled private enabled), desc: 'Repository access level. One of `disabled`, `private` or `enabled`'
optional :merge_requests_access_level, type: String, values: %w(disabled private enabled), desc: 'Merge requests access level. One of `disabled`, `private` or `enabled`'
optional :forking_access_level, type: String, values: %w(disabled private enabled), desc: 'Forks access level. One of `disabled`, `private` or `enabled`'
optional :wiki_access_level, type: String, values: %w(disabled private enabled), desc: 'Wiki access level. One of `disabled`, `private` or `enabled`'
optional :builds_access_level, type: String, values: %w(disabled private enabled), desc: 'Builds access level. One of `disabled`, `private` or `enabled`'
optional :snippets_access_level, type: String, values: %w(disabled private enabled), desc: 'Snippets access level. One of `disabled`, `private` or `enabled`'
......@@ -104,6 +105,7 @@ module API
:default_branch,
:description,
:emails_disabled,
:forking_access_level,
:issues_access_level,
:lfs_enabled,
:merge_requests_access_level,
......
......@@ -16,10 +16,6 @@ module Gitlab
Gitlab::Graphql::ExternallyPaginatedArray,
Gitlab::Graphql::Connections::ExternallyPaginatedArrayConnection
)
GraphQL::Relay::BaseConnection.register_connection_implementation(
Gitlab::Graphql::Pagination::Relations::OffsetActiveRecordRelation,
Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection
)
end
end
end
......
......@@ -6,7 +6,7 @@
module Gitlab
module Graphql
module Pagination
class OffsetActiveRecordRelationConnection < GraphQL::Relay::RelationConnection
class OffsetActiveRecordRelationConnection < GraphQL::Pagination::ActiveRecordRelationConnection
end
end
end
......
# frozen_string_literal: true
module Gitlab
module Graphql
module Pagination
module Relations
class OffsetActiveRecordRelation < ::ActiveRecord::Relation
end
end
end
end
end
......@@ -5274,6 +5274,9 @@ msgstr ""
msgid "ContainerRegistry|Automatically remove extra images that aren't designed to be kept."
msgstr ""
msgid "ContainerRegistry|Build an image"
msgstr ""
msgid "ContainerRegistry|Container Registry"
msgstr ""
......@@ -5328,12 +5331,18 @@ msgstr ""
msgid "ContainerRegistry|Last Updated"
msgstr ""
msgid "ContainerRegistry|Login"
msgstr ""
msgid "ContainerRegistry|Missing or insufficient permission, delete button disabled"
msgstr ""
msgid "ContainerRegistry|Number of tags to retain:"
msgstr ""
msgid "ContainerRegistry|Push an image"
msgstr ""
msgid "ContainerRegistry|Quick Start"
msgstr ""
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Environment > Pod Logs', :js do
include KubernetesHelpers
let(:pod_names) { %w(kube-pod) }
let(:pod_name) { pod_names.first }
let(:project) { create(:project, :repository) }
let(:environment) { create(:environment, project: project) }
let(:service) { create(:cluster_platform_kubernetes, :configured) }
before do
create(:cluster, :provided_by_gcp, environment_scope: '*', projects: [project])
create(:deployment, :success, environment: environment)
stub_kubeclient_pods(environment.deployment_namespace)
stub_kubeclient_logs(pod_name, environment.deployment_namespace, container: 'container-0')
sign_in(project.owner)
end
it "shows environments in dropdown" do
create(:environment, project: project)
visit project_logs_path(environment.project, environment_name: environment.name, pod_name: pod_name)
wait_for_requests
page.within('.js-environments-dropdown') do
toggle = find(".dropdown-menu-toggle:not([disabled])")
expect(toggle).to have_content(environment.name)
toggle.click
dropdown_items = find(".dropdown-menu").all(".dropdown-item")
expect(dropdown_items.first).to have_content(environment.name)
expect(dropdown_items.size).to eq(2)
end
end
context 'with logs', :use_clean_rails_memory_store_caching do
it "shows pod logs", :sidekiq_might_not_need_inline do
visit project_logs_path(environment.project, environment_name: environment.name, pod_name: pod_name)
wait_for_requests
page.within('.js-pods-dropdown') do
find(".dropdown-menu-toggle:not([disabled])").click
dropdown_items = find(".dropdown-menu").all(".dropdown-item:not([disabled])")
expect(dropdown_items.size).to eq(1)
dropdown_items.each_with_index do |item, i|
expect(item.text).to eq(pod_names[i])
end
end
expect(page).to have_content("Dec 13 14:04:22.123Z | kube-pod | Log 1 Dec 13 14:04:23.123Z | kube-pod | Log 2 Dec 13 14:04:24.123Z | kube-pod | Log 3")
end
end
end
......@@ -3,6 +3,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
import { GlEmptyState } from '../stubs';
import projectEmptyState from '~/registry/explorer/components/project_empty_state.vue';
import * as getters from '~/registry/explorer/stores/getters';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -23,6 +24,7 @@ describe('Registry Project Empty state', () => {
noContainersImage: 'bazFoo',
},
},
getters,
});
wrapper = shallowMount(projectEmptyState, {
localVue,
......
import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
import { GlDropdown, GlFormGroup, GlFormInputGroup } from '@gitlab/ui';
import * as getters from '~/registry/explorer/stores/getters';
import QuickstartDropdown from '~/registry/explorer/components/quickstart_dropdown.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import {
QUICK_START,
LOGIN_COMMAND_LABEL,
COPY_LOGIN_TITLE,
BUILD_COMMAND_LABEL,
COPY_BUILD_TITLE,
PUSH_COMMAND_LABEL,
COPY_PUSH_TITLE,
} from '~/registry/explorer//constants';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('quickstart_dropdown', () => {
let wrapper;
let store;
const findDropdownButton = () => wrapper.find(GlDropdown);
const findFormGroups = () => wrapper.findAll(GlFormGroup);
const mountComponent = () => {
store = new Vuex.Store({
state: {
config: {
repositoryUrl: 'foo',
registryHostUrlWithPort: 'bar',
},
},
getters,
});
wrapper = mount(QuickstartDropdown, {
localVue,
store,
});
};
beforeEach(() => {
mountComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
store = null;
});
it('shows the correct text on the button', () => {
expect(findDropdownButton().text()).toContain(QUICK_START);
});
describe.each`
index | id | labelText | titleText | getter
${0} | ${'docker-login-btn'} | ${LOGIN_COMMAND_LABEL} | ${COPY_LOGIN_TITLE} | ${'dockerLoginCommand'}
${1} | ${'docker-build-btn'} | ${BUILD_COMMAND_LABEL} | ${COPY_BUILD_TITLE} | ${'dockerBuildCommand'}
${2} | ${'docker-push-btn'} | ${PUSH_COMMAND_LABEL} | ${COPY_PUSH_TITLE} | ${'dockerPushCommand'}
`('form group at $index', ({ index, id, labelText, titleText, getter }) => {
let formGroup;
const findFormInputGroup = parent => parent.find(GlFormInputGroup);
const findClipboardButton = parent => parent.find(ClipboardButton);
beforeEach(() => {
formGroup = findFormGroups().at(index);
});
it('exists', () => {
expect(formGroup.exists()).toBe(true);
});
it(`has a label ${labelText}`, () => {
expect(formGroup.text()).toBe(labelText);
});
it(`contains a form input group with ${id} id and with value equal to ${getter} getter`, () => {
const formInputGroup = findFormInputGroup(formGroup);
expect(formInputGroup.exists()).toBe(true);
expect(formInputGroup.attributes('id')).toBe(id);
expect(formInputGroup.props('value')).toBe(store.getters[getter]);
});
it(`contains a clipboard button with title of ${titleText} and text equal to ${getter} getter`, () => {
const clipBoardButton = findClipboardButton(formGroup);
expect(clipBoardButton.exists()).toBe(true);
expect(clipBoardButton.props('title')).toBe(titleText);
expect(clipBoardButton.props('text')).toBe(store.getters[getter]);
});
});
});
......@@ -3,6 +3,9 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlPagination, GlSkeletonLoader, GlSprintf } from '@gitlab/ui';
import Tracking from '~/tracking';
import component from '~/registry/explorer/pages/list.vue';
import QuickstartDropdown from '~/registry/explorer/components/quickstart_dropdown.vue';
import GroupEmptyState from '~/registry/explorer/components/group_empty_state.vue';
import ProjectEmptyState from '~/registry/explorer/components/project_empty_state.vue';
import store from '~/registry/explorer/stores/';
import { SET_MAIN_LOADING } from '~/registry/explorer/stores/mutation_types/';
import { imagesListResponse } from '../mock_data';
......@@ -24,6 +27,9 @@ describe('List Page', () => {
const findDetailsLink = () => wrapper.find({ ref: 'detailsLink' });
const findClipboardButton = () => wrapper.find({ ref: 'clipboardButton' });
const findPagination = () => wrapper.find(GlPagination);
const findQuickStartDropdown = () => wrapper.find(QuickstartDropdown);
const findProjectEmptyState = () => wrapper.find(ProjectEmptyState);
const findGroupEmptyState = () => wrapper.find(GroupEmptyState);
beforeEach(() => {
wrapper = shallowMount(component, {
......@@ -76,7 +82,7 @@ describe('List Page', () => {
});
});
describe('when isLoading is true', () => {
describe('isLoading is true', () => {
beforeAll(() => store.commit(SET_MAIN_LOADING, true));
afterAll(() => store.commit(SET_MAIN_LOADING, false));
......@@ -88,9 +94,49 @@ describe('List Page', () => {
it('imagesList is not visible', () => {
expect(findImagesList().exists()).toBe(false);
});
it('quick start is not visible', () => {
expect(findQuickStartDropdown().exists()).toBe(false);
});
});
describe('list is empty', () => {
beforeEach(() => {
store.dispatch('receiveImagesListSuccess', { data: [] });
});
it('quick start is not visible', () => {
expect(findQuickStartDropdown().exists()).toBe(false);
});
it('project empty state is visible', () => {
expect(findProjectEmptyState().exists()).toBe(true);
});
describe('is group page is true', () => {
beforeAll(() => {
store.dispatch('setInitialState', { isGroupPage: true });
});
afterAll(() => {
store.dispatch('setInitialState', { isGroupPage: undefined });
});
it('group empty state is visible', () => {
expect(findGroupEmptyState().exists()).toBe(true);
});
it('quick start is not visible', () => {
expect(findQuickStartDropdown().exists()).toBe(false);
});
});
});
describe('list', () => {
describe('list is not empty', () => {
it('quick start is visible', () => {
expect(findQuickStartDropdown().exists()).toBe(true);
});
describe('listElement', () => {
let listElements;
let firstElement;
......
......@@ -31,4 +31,22 @@ describe('Getters RegistryExplorer store', () => {
});
});
});
describe.each`
getter | prefix | configParameter | suffix
${'dockerBuildCommand'} | ${'docker build -t'} | ${'repositoryUrl'} | ${'.'}
${'dockerPushCommand'} | ${'docker push'} | ${'repositoryUrl'} | ${null}
${'dockerLoginCommand'} | ${'docker login'} | ${'registryHostUrlWithPort'} | ${null}
`('$getter', ({ getter, prefix, configParameter, suffix }) => {
beforeEach(() => {
state = {
config: { repositoryUrl: 'foo', registryHostUrlWithPort: 'bar' },
};
});
it(`returns ${prefix} concatenated with ${configParameter} and optionally suffixed with ${suffix}`, () => {
const expectedPieces = [prefix, state.config[configParameter], suffix].filter(p => p);
expect(getters[getter](state)).toBe(expectedPieces.join(' '));
});
});
});
......@@ -52,12 +52,6 @@ describe GitlabSchema do
expect(connection).to eq(Gitlab::Graphql::Connections::FilterableArrayConnection)
end
it 'paginates OffsetActiveRecordRelation using `Pagination::OffsetActiveRecordRelationConnection`' do
connection = implementations[Gitlab::Graphql::Pagination::Relations::OffsetActiveRecordRelation.name]
expect(connection).to eq(Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection)
end
describe '.execute' do
context 'for different types of users' do
context 'when no context' do
......
......@@ -4,6 +4,6 @@ require 'spec_helper'
describe Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection do
it 'subclasses from GraphQL::Relay::RelationConnection' do
expect(described_class.superclass).to eq GraphQL::Relay::RelationConnection
expect(described_class.superclass).to eq GraphQL::Pagination::ActiveRecordRelationConnection
end
end
......@@ -726,6 +726,7 @@ describe API::Projects do
issues_enabled: false,
jobs_enabled: false,
merge_requests_enabled: false,
forking_access_level: 'disabled',
wiki_enabled: false,
resolve_outdated_diff_discussions: false,
remove_source_branch_after_merge: true,
......@@ -1400,6 +1401,7 @@ describe API::Projects do
expect(json_response['repository_access_level']).to be_present
expect(json_response['issues_access_level']).to be_present
expect(json_response['merge_requests_access_level']).to be_present
expect(json_response['forking_access_level']).to be_present
expect(json_response['wiki_access_level']).to be_present
expect(json_response['builds_access_level']).to be_present
expect(json_response).to have_key('emails_disabled')
......
# frozen_string_literal: true
require 'spec_helper'
describe 'admin/users/_user.html.haml' do
before do
allow(view).to receive(:user).and_return(user)
end
context 'internal users' do
context 'when showing a `Ghost User`' do
let(:user) { create(:user, ghost: true) }
it 'does not render action buttons' do
render
expect(rendered).not_to have_selector('.table-action-buttons')
end
end
context 'when showing a `Bot User`' do
let(:user) { create(:user, user_type: :alert_bot) }
it 'does not render action buttons' do
render
expect(rendered).not_to have_selector('.table-action-buttons')
end
end
end
context 'when showing an external user' do
let(:user) { create(:user) }
it 'renders action buttons' do
render
expect(rendered).to have_selector('.table-action-buttons')
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment