Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-04-22 18:09:56 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-04-22 18:09:56 +0300
commit5f5f492fe278f3322e9533b617522321e2ccafcc (patch)
tree2f9f65c206d555d44034d386f4a03e0835059b04
parent4b074c5f634f8e1e550107f9e8237f07878ca0e8 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/badges/components/badge_list.vue5
-rw-r--r--app/assets/javascripts/deploy_keys/components/action_btn.vue29
-rw-r--r--app/assets/javascripts/deploy_keys/components/app.vue41
-rw-r--r--app/assets/javascripts/deploy_keys/components/confirm_modal.vue46
-rw-r--r--app/assets/javascripts/deploy_keys/components/key.vue46
-rw-r--r--app/assets/javascripts/ensure_data.js4
-rw-r--r--app/assets/javascripts/lib/graphql.js30
-rw-r--r--app/assets/javascripts/members/components/table/members_table.vue3
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue64
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue14
-rw-r--r--app/controllers/projects/blame_controller.rb2
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/commit_controller.rb2
-rw-r--r--app/controllers/projects/compare_controller.rb2
-rw-r--r--app/graphql/types/ci/job_type.rb18
-rw-r--r--app/helpers/avatars_helper.rb11
-rw-r--r--app/models/service.rb8
-rw-r--r--changelogs/unreleased/300874-follow-up-from-trying-to-open-ci-cd-settings-on-gitlab-project-fai.yml5
-rw-r--r--changelogs/unreleased/326079-add-group-level-and-instance-level-helpers-to-service-model.yml5
-rw-r--r--changelogs/unreleased/afontaine-alert-users-when-deleting-deploy-keys.yml5
-rw-r--r--changelogs/unreleased/dz-api-filter-projects-by-topic.yml5
-rw-r--r--changelogs/unreleased/gitlab-ui-integration-883-upgrade-bootstrap-vue-2-15-0.yml5
-rw-r--r--changelogs/unreleased/gl-badge-badges.yml5
-rw-r--r--changelogs/unreleased/pb-add-missing-data-to-ci-job-type.yml5
-rw-r--r--changelogs/unreleased/pb-move-artifacts-to-ellipsis-dropdown.yml5
-rw-r--r--doc/administration/incoming_email.md2
-rw-r--r--doc/api/graphql/reference/index.md3
-rw-r--r--doc/api/projects.md1
-rw-r--r--doc/development/fe_guide/graphql.md2
-rw-r--r--doc/development/snowplow/index.md14
-rw-r--r--doc/development/usage_ping/index.md6
-rw-r--r--doc/operations/incident_management/alerts.md11
-rw-r--r--doc/operations/incident_management/img/alert_detail_added_todo_v13_1.pngbin18170 -> 0 bytes
-rw-r--r--doc/operations/incident_management/img/alert_details_assignees_v13_1.pngbin31091 -> 28017 bytes
-rw-r--r--doc/operations/incident_management/img/alert_list_assignees_v13_1.pngbin29011 -> 0 bytes
-rw-r--r--doc/operations/incident_management/img/alert_todo_assignees_v13_1.pngbin10157 -> 0 bytes
-rw-r--r--doc/ssh/README.md2
-rw-r--r--doc/user/profile/notifications.md8
-rw-r--r--doc/user/project/deploy_keys/index.md18
-rw-r--r--doc/user/project/issues/managing_issues.md5
-rw-r--r--doc/user/project/labels.md2
-rw-r--r--fixtures/lib/gitlab/graphql/queries/plans.customer.mutation.graphql5
-rw-r--r--fixtures/lib/gitlab/graphql/queries/plans.customer.query.graphql5
-rw-r--r--lib/api/helpers.rb18
-rw-r--r--lib/api/projects.rb1
-rw-r--r--lib/gitlab/graphql/queries.rb7
-rw-r--r--locale/gitlab.pot12
-rw-r--r--package.json6
-rw-r--r--spec/features/projects/deploy_keys_spec.rb3
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb16
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb3
-rw-r--r--spec/frontend/__helpers__/mock_apollo_helper.js13
-rw-r--r--spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap2
-rw-r--r--spec/frontend/deploy_keys/components/action_btn_spec.js29
-rw-r--r--spec/frontend/deploy_keys/components/app_spec.js20
-rw-r--r--spec/frontend/deploy_keys/components/confirm_modal_spec.js28
-rw-r--r--spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap12
-rw-r--r--spec/frontend/pipelines/pipeline_multi_actions_spec.js67
-rw-r--r--spec/graphql/types/ci/job_type_spec.rb3
-rw-r--r--spec/helpers/avatars_helper_spec.rb29
-rw-r--r--spec/lib/gitlab/graphql/queries_spec.rb17
-rw-r--r--spec/models/service_spec.rb20
-rw-r--r--spec/requests/api/projects_spec.rb46
-rw-r--r--yarn.lock36
65 files changed, 674 insertions, 167 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 781f69d811b..4933792c6cb 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-a7bc2f86b507daaaf9f18e0ea189b062d6149720
+c2c12e3152bfc6c899b5ad08974659ac7b450232
diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue
index f16a547e441..86c7b4c7a6e 100644
--- a/app/assets/javascripts/badges/components/badge_list.vue
+++ b/app/assets/javascripts/badges/components/badge_list.vue
@@ -1,5 +1,5 @@
<script>
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlLoadingIcon, GlBadge } from '@gitlab/ui';
import { mapState } from 'vuex';
import { GROUP_BADGE } from '../constants';
import BadgeListRow from './badge_list_row.vue';
@@ -9,6 +9,7 @@ export default {
components: {
BadgeListRow,
GlLoadingIcon,
+ GlBadge,
},
computed: {
...mapState(['badges', 'isLoading', 'kind']),
@@ -26,7 +27,7 @@ export default {
<div class="card">
<div class="card-header">
{{ s__('Badges|Your badges') }}
- <span v-show="!isLoading" class="badge badge-pill">{{ badges.length }}</span>
+ <gl-badge v-show="!isLoading" size="sm">{{ badges.length }}</gl-badge>
</div>
<gl-loading-icon v-show="isLoading" size="lg" class="card-body" />
<div v-if="hasNoBadges" class="card-body">
diff --git a/app/assets/javascripts/deploy_keys/components/action_btn.vue b/app/assets/javascripts/deploy_keys/components/action_btn.vue
index af7c391ab70..7bc1eb5d652 100644
--- a/app/assets/javascripts/deploy_keys/components/action_btn.vue
+++ b/app/assets/javascripts/deploy_keys/components/action_btn.vue
@@ -1,10 +1,10 @@
<script>
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlButton } from '@gitlab/ui';
import eventHub from '../eventhub';
export default {
components: {
- GlLoadingIcon,
+ GlButton,
},
props: {
deployKey: {
@@ -15,10 +15,20 @@ export default {
type: String,
required: true,
},
- btnCssClass: {
+ category: {
type: String,
required: false,
- default: 'btn-default',
+ default: 'tertiary',
+ },
+ variant: {
+ type: String,
+ required: false,
+ default: 'default',
+ },
+ icon: {
+ type: String,
+ required: false,
+ default: '',
},
},
data() {
@@ -39,13 +49,14 @@ export default {
</script>
<template>
- <button
- :class="[{ disabled: isLoading }, btnCssClass]"
- :disabled="isLoading"
+ <gl-button
+ :category="category"
+ :variant="variant"
+ :icon="icon"
+ :loading="isLoading"
class="btn"
@click="doAction"
>
<slot></slot>
- <gl-loading-icon v-if="isLoading" :inline="true" />
- </button>
+ </gl-button>
</template>
diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue
index 425cca13ae8..5ea29a7503b 100644
--- a/app/assets/javascripts/deploy_keys/components/app.vue
+++ b/app/assets/javascripts/deploy_keys/components/app.vue
@@ -6,10 +6,12 @@ import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import eventHub from '../eventhub';
import DeployKeysService from '../service';
import DeployKeysStore from '../store';
+import ConfirmModal from './confirm_modal.vue';
import KeysPanel from './keys_panel.vue';
export default {
components: {
+ ConfirmModal,
KeysPanel,
NavigationTabs,
GlLoadingIcon,
@@ -30,6 +32,9 @@ export default {
currentTab: 'enabled_keys',
isLoading: false,
store: new DeployKeysStore(),
+ removeKey: () => {},
+ cancel: () => {},
+ confirmModalVisible: false,
};
},
scopes: {
@@ -61,16 +66,16 @@ export default {
this.service = new DeployKeysService(this.endpoint);
eventHub.$on('enable.key', this.enableKey);
- eventHub.$on('remove.key', this.disableKey);
- eventHub.$on('disable.key', this.disableKey);
+ eventHub.$on('remove.key', this.confirmRemoveKey);
+ eventHub.$on('disable.key', this.confirmRemoveKey);
},
mounted() {
this.fetchKeys();
},
beforeDestroy() {
eventHub.$off('enable.key', this.enableKey);
- eventHub.$off('remove.key', this.disableKey);
- eventHub.$off('disable.key', this.disableKey);
+ eventHub.$off('remove.key', this.confirmRemoveKey);
+ eventHub.$off('disable.key', this.confirmRemoveKey);
},
methods: {
onChangeTab(tab) {
@@ -97,19 +102,20 @@ export default {
.then(this.fetchKeys)
.catch(() => new Flash(s__('DeployKeys|Error enabling deploy key')));
},
- disableKey(deployKey, callback) {
- if (
- // eslint-disable-next-line no-alert
- window.confirm(s__('DeployKeys|You are going to remove this deploy key. Are you sure?'))
- ) {
+ confirmRemoveKey(deployKey, callback) {
+ const hideModal = () => {
+ this.confirmModalVisible = false;
+ callback?.();
+ };
+ this.removeKey = () => {
this.service
.disableKey(deployKey.id)
.then(this.fetchKeys)
- .then(callback)
+ .then(hideModal)
.catch(() => new Flash(s__('DeployKeys|Error removing deploy key')));
- } else {
- callback();
- }
+ };
+ this.cancel = hideModal;
+ this.confirmModalVisible = true;
},
},
};
@@ -117,6 +123,7 @@ export default {
<template>
<div class="gl-mb-3 deploy-keys">
+ <confirm-modal :visible="confirmModalVisible" @remove="removeKey" @cancel="cancel" />
<gl-loading-icon
v-if="isLoading && !hasKeys"
:label="s__('DeployKeys|Loading deploy keys')"
@@ -124,8 +131,12 @@ export default {
/>
<template v-else-if="hasKeys">
<div class="top-area scrolling-tabs-container inner-page-scroll-tabs">
- <div class="fade-left"><gl-icon name="chevron-lg-left" :size="12" /></div>
- <div class="fade-right"><gl-icon name="chevron-lg-right" :size="12" /></div>
+ <div class="fade-left">
+ <gl-icon name="chevron-lg-left" :size="12" />
+ </div>
+ <div class="fade-right">
+ <gl-icon name="chevron-lg-right" :size="12" />
+ </div>
<navigation-tabs :tabs="tabs" scope="deployKeys" @onChangeTab="onChangeTab" />
</div>
diff --git a/app/assets/javascripts/deploy_keys/components/confirm_modal.vue b/app/assets/javascripts/deploy_keys/components/confirm_modal.vue
new file mode 100644
index 00000000000..1932435c42a
--- /dev/null
+++ b/app/assets/javascripts/deploy_keys/components/confirm_modal.vue
@@ -0,0 +1,46 @@
+<script>
+import { GlModal } from '@gitlab/ui';
+import { __ } from '~/locale';
+
+export default {
+ components: {
+ GlModal,
+ },
+ props: {
+ visible: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ i18n: {
+ body: __(
+ 'Are you sure you want to remove this deploy key? If anything is still using this key, it will stop working.',
+ ),
+ },
+ modalOptions: {
+ title: __('Do you want to remove this deploy key?'),
+ actionPrimary: {
+ text: __('Remove deploy key'),
+ attributes: [{ variant: 'danger' }],
+ },
+ actionSecondary: {
+ text: __('Cancel'),
+ attributes: [{ category: 'tertiary' }],
+ },
+ static: true,
+ modalId: 'confirm-remove-deploy-key',
+ },
+};
+</script>
+<template>
+ <gl-modal
+ v-bind="$options.modalOptions"
+ :visible="visible"
+ @primary="$emit('remove')"
+ @secondary="$emit('cancel')"
+ @hidden="$emit('cancel')"
+ >
+ {{ $options.i18n.body }}
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue
index e70ca18bb71..fb55e86df1f 100644
--- a/app/assets/javascripts/deploy_keys/components/key.vue
+++ b/app/assets/javascripts/deploy_keys/components/key.vue
@@ -1,5 +1,5 @@
<script>
-import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import { GlIcon, GlLink, GlTooltipDirective, GlButton } from '@gitlab/ui';
import { head, tail } from 'lodash';
import { s__, sprintf } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
@@ -9,7 +9,9 @@ import actionBtn from './action_btn.vue';
export default {
components: {
actionBtn,
+ GlButton,
GlIcon,
+ GlLink,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -123,15 +125,15 @@ export default {
<div role="rowheader" class="table-mobile-header">{{ s__('DeployKeys|Project usage') }}</div>
<div class="table-mobile-content deploy-project-list">
<template v-if="projects.length > 0">
- <a
+ <gl-link
v-gl-tooltip
:title="projectTooltipTitle(firstProject)"
class="label deploy-project-label"
>
<span> {{ firstProject.project.full_name }} </span>
<gl-icon :name="firstProject.can_push ? 'lock-open' : 'lock'" />
- </a>
- <a
+ </gl-link>
+ <gl-link
v-if="isExpandable"
v-gl-tooltip
:title="restProjectsTooltip"
@@ -139,8 +141,8 @@ export default {
@click="toggleExpanded"
>
<span>{{ restProjectsLabel }}</span>
- </a>
- <a
+ </gl-link>
+ <gl-link
v-for="deployKeysProject in restProjects"
v-else-if="isExpanded"
:key="deployKeysProject.project.full_path"
@@ -151,7 +153,7 @@ export default {
>
<span> {{ deployKeysProject.project.full_name }} </span>
<gl-icon :name="deployKeysProject.can_push ? 'lock-open' : 'lock'" />
- </a>
+ </gl-link>
</template>
<span v-else class="text-secondary">{{ __('None') }}</span>
</div>
@@ -166,41 +168,43 @@ export default {
</div>
<div class="table-section section-15 table-button-footer deploy-key-actions">
<div class="btn-group table-action-buttons">
- <action-btn v-if="!isEnabled" :deploy-key="deployKey" type="enable">
+ <action-btn v-if="!isEnabled" :deploy-key="deployKey" type="enable" category="secondary">
{{ __('Enable') }}
</action-btn>
- <a
+ <gl-button
v-if="deployKey.can_edit"
v-gl-tooltip
:href="editDeployKeyPath"
:title="__('Edit')"
- class="btn btn-default text-secondary"
+ :aria-label="__('Edit')"
data-container="body"
- >
- <gl-icon name="pencil" />
- </a>
+ icon="pencil"
+ category="secondary"
+ />
<action-btn
v-if="isRemovable"
v-gl-tooltip
:deploy-key="deployKey"
:title="__('Remove')"
- btn-css-class="btn-danger"
+ :aria-label="__('Remove')"
+ category="primary"
+ variant="danger"
+ icon="remove"
type="remove"
data-container="body"
- >
- <gl-icon name="remove" />
- </action-btn>
+ />
<action-btn
v-else-if="isEnabled"
v-gl-tooltip
:deploy-key="deployKey"
:title="__('Disable')"
- btn-css-class="btn-warning"
+ :aria-label="__('Disable')"
type="disable"
data-container="body"
- >
- <gl-icon name="cancel" />
- </action-btn>
+ icon="cancel"
+ category="primary"
+ variant="danger"
+ />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/ensure_data.js b/app/assets/javascripts/ensure_data.js
index 5b4d1afc9d0..69c81c35bd4 100644
--- a/app/assets/javascripts/ensure_data.js
+++ b/app/assets/javascripts/ensure_data.js
@@ -3,8 +3,8 @@ import { GlEmptyState } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { __ } from '~/locale';
-const ERROR_FETCHING_DATA_HEADER = __('Could not get the data properly');
-const ERROR_FETCHING_DATA_DESCRIPTION = __(
+export const ERROR_FETCHING_DATA_HEADER = __('Could not get the data properly');
+export const ERROR_FETCHING_DATA_DESCRIPTION = __(
'Please try and refresh the page. If the problem persists please contact support.',
);
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index c720476f3bf..1630f0d689c 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -18,11 +18,21 @@ export const fetchPolicies = {
};
export default (resolvers = {}, config = {}) => {
- let uri = `${gon.relative_url_root || ''}/api/graphql`;
+ const {
+ assumeImmutableResults,
+ baseUrl,
+ batchMax = 10,
+ cacheConfig,
+ fetchPolicy = fetchPolicies.CACHE_FIRST,
+ typeDefs,
+ path = '/api/graphql',
+ useGet = false,
+ } = config;
+ let uri = `${gon.relative_url_root || ''}${path}`;
- if (config.baseUrl) {
+ if (baseUrl) {
// Prepend baseUrl and ensure that `///` are replaced with `/`
- uri = `${config.baseUrl}${uri}`.replace(/\/{3,}/g, '/');
+ uri = `${baseUrl}${uri}`.replace(/\/{3,}/g, '/');
}
const httpOptions = {
@@ -34,7 +44,7 @@ export default (resolvers = {}, config = {}) => {
// We set to `same-origin` which is default value in modern browsers.
// See https://github.com/whatwg/fetch/pull/585 for more information.
credentials: 'same-origin',
- batchMax: config.batchMax || 10,
+ batchMax,
};
const requestCounterLink = new ApolloLink((operation, forward) => {
@@ -50,7 +60,7 @@ export default (resolvers = {}, config = {}) => {
const uploadsLink = ApolloLink.split(
(operation) => operation.getContext().hasUpload || operation.getContext().isSingleRequest,
createUploadLink(httpOptions),
- config.useGet ? createHttpLink(httpOptions) : new BatchHttpLink(httpOptions),
+ useGet ? createHttpLink(httpOptions) : new BatchHttpLink(httpOptions),
);
const performanceBarLink = new ApolloLink((operation, forward) => {
@@ -74,7 +84,7 @@ export default (resolvers = {}, config = {}) => {
});
return new ApolloClient({
- typeDefs: config.typeDefs,
+ typeDefs,
link: ApolloLink.from([
requestCounterLink,
performanceBarLink,
@@ -83,14 +93,14 @@ export default (resolvers = {}, config = {}) => {
uploadsLink,
]),
cache: new InMemoryCache({
- ...config.cacheConfig,
- freezeResults: config.assumeImmutableResults,
+ ...cacheConfig,
+ freezeResults: assumeImmutableResults,
}),
resolvers,
- assumeImmutableResults: config.assumeImmutableResults,
+ assumeImmutableResults,
defaultOptions: {
query: {
- fetchPolicy: config.fetchPolicy || fetchPolicies.CACHE_FIRST,
+ fetchPolicy,
},
},
});
diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue
index c9ac9da0501..09ef98ec411 100644
--- a/app/assets/javascripts/members/components/table/members_table.vue
+++ b/app/assets/javascripts/members/components/table/members_table.vue
@@ -134,6 +134,9 @@ export default {
show-empty
:tbody-tr-attr="tbodyTrAttr"
>
+ <template #head()="{ label }">
+ {{ label }}
+ </template>
<template #cell(account)="{ item: member }">
<members-table-cell #default="{ memberType, isCurrentUser }" :member="member">
<member-avatar
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue
new file mode 100644
index 00000000000..73cd8742a27
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue
@@ -0,0 +1,64 @@
+<script>
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownSectionHeader,
+ GlSprintf,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+import { __ } from '~/locale';
+
+export default {
+ i18n: {
+ artifacts: __('Artifacts'),
+ downloadArtifact: __('Download %{name} artifact'),
+ artifactSectionHeader: __('Download artifacts'),
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownSectionHeader,
+ GlSprintf,
+ },
+ props: {
+ artifacts: {
+ type: Array,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <gl-dropdown
+ v-gl-tooltip
+ :title="$options.i18n.artifacts"
+ :text="$options.i18n.artifacts"
+ :aria-label="$options.i18n.artifacts"
+ icon="ellipsis_v"
+ data-testid="pipeline-multi-actions-dropdown"
+ right
+ lazy
+ text-sr-only
+ no-caret
+ >
+ <gl-dropdown-section-header>{{
+ $options.i18n.artifactSectionHeader
+ }}</gl-dropdown-section-header>
+
+ <gl-dropdown-item
+ v-for="(artifact, i) in artifacts"
+ :key="i"
+ :href="artifact.path"
+ rel="nofollow"
+ download
+ data-testid="artifact-item"
+ >
+ <gl-sprintf :message="$options.i18n.downloadArtifact">
+ <template #name>{{ artifact.name }}</template>
+ </gl-sprintf>
+ </gl-dropdown-item>
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
index 81eeead2171..a0f8e5272cf 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
@@ -2,7 +2,7 @@
import { GlButton, GlTooltipDirective, GlModalDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import eventHub from '../../event_hub';
-import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
+import PipelineMultiActions from './pipeline_multi_actions.vue';
import PipelinesManualActions from './pipelines_manual_actions.vue';
export default {
@@ -16,8 +16,8 @@ export default {
},
components: {
GlButton,
+ PipelineMultiActions,
PipelinesManualActions,
- PipelinesArtifactsComponent,
},
props: {
pipeline: {
@@ -80,11 +80,6 @@ export default {
<div class="btn-group">
<pipelines-manual-actions v-if="actions.length > 0" :actions="actions" />
- <pipelines-artifacts-component
- v-if="pipeline.details.artifacts.length"
- :artifacts="pipeline.details.artifacts"
- />
-
<gl-button
v-if="pipeline.flags.retryable"
v-gl-tooltip.hover
@@ -114,6 +109,11 @@ export default {
class="js-pipelines-cancel-button"
@click="handleCancelClick"
/>
+
+ <pipeline-multi-actions
+ v-if="pipeline.details.artifacts.length"
+ :artifacts="pipeline.details.artifacts"
+ />
</div>
</div>
</template>
diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb
index 14fc2cd5a00..1df7b9ed165 100644
--- a/app/controllers/projects/blame_controller.rb
+++ b/app/controllers/projects/blame_controller.rb
@@ -20,7 +20,7 @@ class Projects::BlameController < Projects::ApplicationController
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
environment_params[:find_latest] = true
- @environment = Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
+ @environment = ::Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
@blame = Gitlab::Blame.new(@blob, @commit)
@blame = Gitlab::View::Presenter::Factory.new(@blame, project: @project, path: @path).fabricate!
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index b9b0c2b802c..2b2b4bfb65a 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -214,7 +214,7 @@ class Projects::BlobController < Projects::ApplicationController
def show_html
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
environment_params[:find_latest] = true
- @environment = Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
+ @environment = ::Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
@last_commit = @repository.last_commit_for_path(@commit.id, @blob.path, literal_pathspec: true)
@code_navigation_path = Gitlab::CodeNavigationPath.new(@project, @blob.commit_id).full_json_path_for(@blob.path)
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index be1bd37e341..863715429ff 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -167,7 +167,7 @@ class Projects::CommitController < Projects::ApplicationController
@diffs = commit.diffs(opts)
@notes_count = commit.notes.count
- @environment = Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, commit: @commit, find_latest: true).execute.last
+ @environment = ::Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, commit: @commit, find_latest: true).execute.last
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 0db9940529a..28a87f83451 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -136,7 +136,7 @@ class Projects::CompareController < Projects::ApplicationController
if compare
environment_params = source_project.repository.branch_exists?(head_ref) ? { ref: head_ref } : { commit: compare.commit }
environment_params[:find_latest] = true
- @environment = Environments::EnvironmentsByDeploymentsFinder.new(source_project, current_user, environment_params).execute.last
+ @environment = ::Environments::EnvironmentsByDeploymentsFinder.new(source_project, current_user, environment_params).execute.last
end
end
diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb
index 94a256fed3d..a3b8a132427 100644
--- a/app/graphql/types/ci/job_type.rb
+++ b/app/graphql/types/ci/job_type.rb
@@ -65,6 +65,12 @@ module Types
description: 'Indicates the job is active.'
field :coverage, GraphQL::FLOAT_TYPE, null: true,
description: 'Coverage level of the job.'
+ field :created_by_tag, GraphQL::BOOLEAN_TYPE, null: false,
+ description: 'Whether the job was created by a tag.'
+ field :manual_job, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Whether the job has a manual action.'
+ field :triggered, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'Whether the job was triggered.'
def pipeline
Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Pipeline, object.pipeline_id).find
@@ -123,6 +129,18 @@ module Types
def coverage
object&.coverage
end
+
+ def created_by_tag
+ object.tag?
+ end
+
+ def manual_job
+ object.try(:action?)
+ end
+
+ def triggered
+ object.try(:trigger_request)
+ end
end
end
end
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index 09f91f350bd..33698897083 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -98,6 +98,14 @@ module AvatarsHelper
end
end
+ def avatar_without_link(resource, options = {})
+ if resource.is_a?(User)
+ user_avatar_without_link(options.merge(user: resource))
+ elsif resource.is_a?(Group)
+ group_icon(resource, options.merge(class: 'avatar'))
+ end
+ end
+
private
def avatar_icon_by_user_email_or_gravatar(email, size, scale, only_path:)
@@ -136,9 +144,10 @@ module AvatarsHelper
def source_identicon(source, options = {})
bg_key = (source.id % 7) + 1
+ size_class = "s#{options[:size]}" if options[:size]
options[:class] =
- [*options[:class], "identicon bg#{bg_key}"].join(' ')
+ [*options[:class], "identicon bg#{bg_key}", size_class].compact.join(' ')
content_tag(:div, class: options[:class].strip) do
source.name[0, 1].upcase
diff --git a/app/models/service.rb b/app/models/service.rb
index 9867081ce1b..7782b016b52 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -416,6 +416,14 @@ class Service < ApplicationRecord
project_id.present?
end
+ def group_level?
+ group_id.present?
+ end
+
+ def instance_level?
+ instance?
+ end
+
def parent
project || group
end
diff --git a/changelogs/unreleased/300874-follow-up-from-trying-to-open-ci-cd-settings-on-gitlab-project-fai.yml b/changelogs/unreleased/300874-follow-up-from-trying-to-open-ci-cd-settings-on-gitlab-project-fai.yml
new file mode 100644
index 00000000000..07c15343d09
--- /dev/null
+++ b/changelogs/unreleased/300874-follow-up-from-trying-to-open-ci-cd-settings-on-gitlab-project-fai.yml
@@ -0,0 +1,5 @@
+---
+title: Add generic avatar method for users and groups
+merge_request: 59758
+author:
+type: fixed
diff --git a/changelogs/unreleased/326079-add-group-level-and-instance-level-helpers-to-service-model.yml b/changelogs/unreleased/326079-add-group-level-and-instance-level-helpers-to-service-model.yml
new file mode 100644
index 00000000000..f5202dd33c4
--- /dev/null
+++ b/changelogs/unreleased/326079-add-group-level-and-instance-level-helpers-to-service-model.yml
@@ -0,0 +1,5 @@
+---
+title: Add group_level? and instance_level? helpers to Service model
+merge_request: 59838
+author: Amit Patel @amit.savani
+type: other
diff --git a/changelogs/unreleased/afontaine-alert-users-when-deleting-deploy-keys.yml b/changelogs/unreleased/afontaine-alert-users-when-deleting-deploy-keys.yml
new file mode 100644
index 00000000000..c02a4e88348
--- /dev/null
+++ b/changelogs/unreleased/afontaine-alert-users-when-deleting-deploy-keys.yml
@@ -0,0 +1,5 @@
+---
+title: Use GlModal for Confirmation of Deploy Key Delete
+merge_request: 59697
+author:
+type: changed
diff --git a/changelogs/unreleased/dz-api-filter-projects-by-topic.yml b/changelogs/unreleased/dz-api-filter-projects-by-topic.yml
new file mode 100644
index 00000000000..6001563c04c
--- /dev/null
+++ b/changelogs/unreleased/dz-api-filter-projects-by-topic.yml
@@ -0,0 +1,5 @@
+---
+title: Search projects by topic via API
+merge_request: 59900
+author:
+type: added
diff --git a/changelogs/unreleased/gitlab-ui-integration-883-upgrade-bootstrap-vue-2-15-0.yml b/changelogs/unreleased/gitlab-ui-integration-883-upgrade-bootstrap-vue-2-15-0.yml
new file mode 100644
index 00000000000..0278a78c78b
--- /dev/null
+++ b/changelogs/unreleased/gitlab-ui-integration-883-upgrade-bootstrap-vue-2-15-0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Bootstrap to v4.5.3
+merge_request: 59501
+author:
+type: other
diff --git a/changelogs/unreleased/gl-badge-badges.yml b/changelogs/unreleased/gl-badge-badges.yml
new file mode 100644
index 00000000000..1a437fd3e8d
--- /dev/null
+++ b/changelogs/unreleased/gl-badge-badges.yml
@@ -0,0 +1,5 @@
+---
+title: Move badge to vue component in project badges
+merge_request: 58045
+author: Yogi (@yo)
+type: changed
diff --git a/changelogs/unreleased/pb-add-missing-data-to-ci-job-type.yml b/changelogs/unreleased/pb-add-missing-data-to-ci-job-type.yml
new file mode 100644
index 00000000000..c5bd7a7210e
--- /dev/null
+++ b/changelogs/unreleased/pb-add-missing-data-to-ci-job-type.yml
@@ -0,0 +1,5 @@
+---
+title: Add missing data to CiJob type
+merge_request: 59805
+author:
+type: added
diff --git a/changelogs/unreleased/pb-move-artifacts-to-ellipsis-dropdown.yml b/changelogs/unreleased/pb-move-artifacts-to-ellipsis-dropdown.yml
new file mode 100644
index 00000000000..7ef3617ad85
--- /dev/null
+++ b/changelogs/unreleased/pb-move-artifacts-to-ellipsis-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Change artifacts download button to a vertical ellipsis menu
+merge_request: 59667
+author:
+type: changed
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index 22cd6ca097c..693c22f925c 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -4,7 +4,7 @@ group: Project Management
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Incoming email
+# Incoming email **(FREE SELF)**
GitLab has several features based on receiving incoming emails:
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 1cb82fbb0d8..0fd45a3c37c 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -7075,10 +7075,12 @@ Represents the total number of issues and their weights for a particular day.
| <a id="cijobcommitpath"></a>`commitPath` | [`String`](#string) | Path to the commit that triggered the job. |
| <a id="cijobcoverage"></a>`coverage` | [`Float`](#float) | Coverage level of the job. |
| <a id="cijobcreatedat"></a>`createdAt` | [`Time!`](#time) | When the job was created. |
+| <a id="cijobcreatedbytag"></a>`createdByTag` | [`Boolean!`](#boolean) | Whether the job was created by a tag. |
| <a id="cijobdetailedstatus"></a>`detailedStatus` | [`DetailedStatus`](#detailedstatus) | Detailed status of the job. |
| <a id="cijobduration"></a>`duration` | [`Int`](#int) | Duration of the job in seconds. |
| <a id="cijobfinishedat"></a>`finishedAt` | [`Time`](#time) | When a job has finished running. |
| <a id="cijobid"></a>`id` | [`JobID`](#jobid) | ID of the job. |
+| <a id="cijobmanualjob"></a>`manualJob` | [`Boolean`](#boolean) | Whether the job has a manual action. |
| <a id="cijobname"></a>`name` | [`String`](#string) | Name of the job. |
| <a id="cijobneeds"></a>`needs` | [`CiBuildNeedConnection`](#cibuildneedconnection) | References to builds that must complete before the jobs run. |
| <a id="cijobpipeline"></a>`pipeline` | [`Pipeline`](#pipeline) | Pipeline the job belongs to. |
@@ -7094,6 +7096,7 @@ Represents the total number of issues and their weights for a particular day.
| <a id="cijobstartedat"></a>`startedAt` | [`Time`](#time) | When the job was started. |
| <a id="cijobstatus"></a>`status` | [`CiJobStatus`](#cijobstatus) | Status of the job. |
| <a id="cijobtags"></a>`tags` | [`[String!]`](#string) | Tags for the current job. |
+| <a id="cijobtriggered"></a>`triggered` | [`Boolean`](#boolean) | Whether the job was triggered. |
### `CiJobArtifact`
diff --git a/doc/api/projects.md b/doc/api/projects.md
index d9aabfbc337..2961cb725b3 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -59,6 +59,7 @@ GET /projects
| `sort` | string | **{dotted-circle}** No | Return projects sorted in `asc` or `desc` order. Default is `desc`. |
| `starred` | boolean | **{dotted-circle}** No | Limit by projects starred by the current user. |
| `statistics` | boolean | **{dotted-circle}** No | Include project statistics. |
+| `topic` | string | **{dotted-circle}** No | Comma-separated topic names. Limit results to projects that match all of given topics. See `tag_list` attribute. |
| `visibility` | string | **{dotted-circle}** No | Limit by visibility `public`, `internal`, or `private`. |
| `wiki_checksum_failed` **(PREMIUM)** | boolean | **{dotted-circle}** No | Limit projects where the wiki checksum calculation has failed ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6137) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.2). |
| `with_custom_attributes` | boolean | **{dotted-circle}** No | Include [custom attributes](custom_attributes.md) in response. _(admins only)_ |
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
index e87b4197269..8ce6db8b88a 100644
--- a/doc/development/fe_guide/graphql.md
+++ b/doc/development/fe_guide/graphql.md
@@ -118,6 +118,8 @@ To distinguish queries from mutations and fragments, the following naming conven
- `add_user.mutation.graphql` for mutations;
- `basic_user.fragment.graphql` for fragments.
+If you are using queries for the [CustomersDot GraphQL endpoint](https://gitlab.com/gitlab-org/gitlab/-/blob/be78ccd832fd40315c5e63bb48ee1596ae146f56/app/controllers/customers_dot/proxy_controller.rb), end the filename with `.customer.query.graphql`, `.customer.mutation.graphql`, or `.customer.fragment.graphql`.
+
### Fragments
[Fragments](https://graphql.org/learn/queries/#fragments) are a way to make your complex GraphQL queries more readable and re-usable. Here is an example of GraphQL fragment:
diff --git a/doc/development/snowplow/index.md b/doc/development/snowplow/index.md
index c07291d61f2..1ffc86b40d5 100644
--- a/doc/development/snowplow/index.md
+++ b/doc/development/snowplow/index.md
@@ -114,13 +114,13 @@ The current method provides several attributes that are sent on each click event
| category* | label | action | property** | value |
|-------------|------------------|-----------------------|----------|:-----:|
-| [root:index] | main_navigation | click_navigation_link | `[link_label]` | - |
-| [groups:boards:show] | toggle_swimlanes | click_toggle_button | - | `[is_active]` |
-| [projects:registry:index] | registry_delete | click_button | - | - |
-| [projects:registry:index] | registry_delete | confirm_deletion | - | - |
-| [projects:blob:show] | congratulate_first_pipeline | click_button | `[human_access]` | - |
-| [projects:clusters:new] | chart_options | generate_link | `[chart_link]` | - |
-| [projects:clusters:new] | chart_options | click_add_label_button | `[label_id]` | - |
+| `[root:index]` | `main_navigation` | `click_navigation_link` | `[link_label]` | - |
+| `[groups:boards:show]` | `toggle_swimlanes` | `click_toggle_button` | - | `[is_active]` |
+| `[projects:registry:index]` | `registry_delete` | `click_button` | - | - |
+| `[projects:registry:index]` | `registry_delete` | `confirm_deletion` | - | - |
+| `[projects:blob:show]` | `congratulate_first_pipeline` | `click_button` | `[human_access]` | - |
+| `[projects:clusters:new]` | `chart_options` | `generate_link` | `[chart_link]` | - |
+| `[projects:clusters:new]` | `chart_options` | `click_add_label_button` | `[label_id]` | - |
_* It's ok to omit the category, and use the default._<br>
_** Property is usually the best place for variable strings._
diff --git a/doc/development/usage_ping/index.md b/doc/development/usage_ping/index.md
index bf423d68700..6a6a2cdd5d8 100644
--- a/doc/development/usage_ping/index.md
+++ b/doc/development/usage_ping/index.md
@@ -913,9 +913,9 @@ On GitLab.com, the Product Intelligence team regularly monitors Usage Ping. They
To set up Usage Ping locally, you must:
-1. [Set up local repositories]#(set-up-local-repositories)
-1. [Test local setup](#test-local-setup)
-1. (Optional) [Test Prometheus-based usage ping](#test-prometheus-based-usage-ping)
+1. [Set up local repositories](#set-up-local-repositories).
+1. [Test local setup](#test-local-setup).
+1. (Optional) [Test Prometheus-based usage ping](#test-prometheus-based-usage-ping).
#### Set up local repositories
diff --git a/doc/operations/incident_management/alerts.md b/doc/operations/incident_management/alerts.md
index 276009ac200..0c7a87d3087 100644
--- a/doc/operations/incident_management/alerts.md
+++ b/doc/operations/incident_management/alerts.md
@@ -167,22 +167,19 @@ difficult to track who is investigating and working on it. Assigning alerts ease
To assign an alert:
-1. To display the list of current alerts, navigate to **Operations > Alerts**:
-
- ![Alert List View Assignee(s)](img/alert_list_assignees_v13_1.png)
+1. To display the list of current alerts, navigate to **Operations > Alerts**.
-1. Select your desired alert to display its **Alert Details View**:
+1. Select your desired alert to display its details.
![Alert Details View Assignee(s)](img/alert_details_assignees_v13_1.png)
1. If the right sidebar is not expanded, select
**{angle-double-right}** **Expand sidebar** to expand it.
+
1. In the right sidebar, locate the **Assignee**, and then select **Edit**.
From the dropdown menu, select each user you want to assign to the alert.
GitLab creates a [to-do item](../../user/todos.md) for each user.
- ![Alert Details View Assignee(s)](img/alert_todo_assignees_v13_1.png)
-
After completing their portion of investigating or fixing the alert, users can
unassign themselves from the alert. To remove an assignee, select **Edit** next to the **Assignee** dropdown menu
and deselect the user from the list of assignees, or select **Unassigned**.
@@ -203,8 +200,6 @@ add a to-do item:
Select the **To-Do List** **{todo-done}** in the navigation bar to view your current to-do list.
-![Alert Details Added to do](img/alert_detail_added_todo_v13_1.png)
-
## Link runbooks to alerts
> Runbook URLs [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39315) in GitLab 13.3.
diff --git a/doc/operations/incident_management/img/alert_detail_added_todo_v13_1.png b/doc/operations/incident_management/img/alert_detail_added_todo_v13_1.png
deleted file mode 100644
index ae874706895..00000000000
--- a/doc/operations/incident_management/img/alert_detail_added_todo_v13_1.png
+++ /dev/null
Binary files differ
diff --git a/doc/operations/incident_management/img/alert_details_assignees_v13_1.png b/doc/operations/incident_management/img/alert_details_assignees_v13_1.png
index dab4eac384a..29cdba2c9ab 100644
--- a/doc/operations/incident_management/img/alert_details_assignees_v13_1.png
+++ b/doc/operations/incident_management/img/alert_details_assignees_v13_1.png
Binary files differ
diff --git a/doc/operations/incident_management/img/alert_list_assignees_v13_1.png b/doc/operations/incident_management/img/alert_list_assignees_v13_1.png
deleted file mode 100644
index db1e0d8dcb7..00000000000
--- a/doc/operations/incident_management/img/alert_list_assignees_v13_1.png
+++ /dev/null
Binary files differ
diff --git a/doc/operations/incident_management/img/alert_todo_assignees_v13_1.png b/doc/operations/incident_management/img/alert_todo_assignees_v13_1.png
deleted file mode 100644
index 637f8be5d25..00000000000
--- a/doc/operations/incident_management/img/alert_todo_assignees_v13_1.png
+++ /dev/null
Binary files differ
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 87213f72534..50743f7438e 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -207,7 +207,7 @@ To use SSH with GitLab, copy your public key to your GitLab account.
1. Sign in to GitLab.
1. In the top right corner, select your avatar.
-1. Select **Settings**.
+1. Select **Preferences**.
1. From the left sidebar, select **SSH Keys**.
1. In the **Key** box, paste the contents of your public key.
If you manually copied the key, make sure you copy the entire key,
diff --git a/doc/user/profile/notifications.md b/doc/user/profile/notifications.md
index 4d890e249e7..e64c7b4f3f0 100644
--- a/doc/user/profile/notifications.md
+++ b/doc/user/profile/notifications.md
@@ -43,7 +43,7 @@ You can tune the notifications you receive by combining your notification settin
To edit your notification settings:
-1. Click on your profile picture and select **Settings**.
+1. Click on your profile picture and select **Preferences**.
1. Click **Notifications** in the left sidebar.
1. Edit the desired notification settings. Edited settings are automatically saved and enabled.
@@ -85,7 +85,7 @@ You can select a notification level for each project to help you closely monitor
To select a notification level for a project, use either of these methods:
-1. Click on your profile picture and select **Settings**.
+1. Click on your profile picture and select **Preferences**.
1. Click **Notifications** in the left sidebar.
1. Locate the project in the **Projects** section.
1. Select the desired [notification level](#notification-levels).
@@ -109,7 +109,7 @@ You can select a notification level and email address for each group.
To select a notification level for a group, use either of these methods:
-1. Click on your profile picture and select **Settings**.
+1. Click on your profile picture and select **Preferences**.
1. Click **Notifications** in the left sidebar.
1. Locate the project in the **Groups** section.
1. Select the desired [notification level](#notification-levels).
@@ -126,7 +126,7 @@ To select a notification level for a group, use either of these methods:
You can select an email address to receive notifications for each group you belong to. This could be useful, for example, if you work freelance, and want to keep email about clients' projects separate.
-1. Click on your profile picture and select **Settings**.
+1. Click on your profile picture and select **Preferences**.
1. Click **Notifications** in the left sidebar.
1. Locate the project in the **Groups** section.
1. Select the desired email address.
diff --git a/doc/user/project/deploy_keys/index.md b/doc/user/project/deploy_keys/index.md
index a45c3d26f1a..bf082cca93a 100644
--- a/doc/user/project/deploy_keys/index.md
+++ b/doc/user/project/deploy_keys/index.md
@@ -151,6 +151,24 @@ Adding a public deploy key does not immediately expose any repository to it. Pub
deploy keys enable access from other systems, but access is not given to any project
until a project maintainer chooses to make use of it.
+## How to disable deploy keys
+
+[Project maintainers and owners](../../permissions.md#project-members-permissions)
+can remove or disable a deploy key for a project repository:
+
+1. Navigate to the project's **Settings > Repository** page.
+1. Expand the **Deploy keys** section.
+1. Select the **{remove}** or **{cancel}** button.
+
+NOTE:
+If anything relies on the removed deploy key, it will stop working once removed.
+
+If the key is **publicly accessible**, it will be removed from the project, but still available under **Publicly accessible deploy keys**.
+
+If the key is **privately accessible** and only in use by this project, it will deleted.
+
+If the key is **privately accessible** and in use by other projects, it will be removed from the project, but still available under **Privately accesible deploy keys**.
+
## Troubleshooting
### Deploy key cannot push to a protected branch
diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md
index 2a841400852..915d049b7c9 100644
--- a/doc/user/project/issues/managing_issues.md
+++ b/doc/user/project/issues/managing_issues.md
@@ -74,8 +74,9 @@ To visit the issue tracker for all projects in your group:
1. Go to the group dashboard.
1. In the left sidebar, select **Issues**.
1. In the top-right, select the **Select project to create issue** button.
-1. Select the project you'd like to create an issue for. The button now appears as **New issue in <selected project>**.
-1. Select **New issue in <selected project>**.
+1. Select the project you'd like to create an issue for. The button now reflects the selected
+ project.
+1. Select the button to create an issue in the selected project.
![Select project to create issue](img/select_project_from_group_level_issue_tracker_v13_11.png)
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index fd5045f2e93..0cb5b5d993e 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -4,7 +4,7 @@ group: Project Management
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Labels
+# Labels **(FREE)**
As your count of issues, merge requests, and epics grows in GitLab, it's more and more challenging
to keep track of those items. Especially as your organization grows from just a few people to
diff --git a/fixtures/lib/gitlab/graphql/queries/plans.customer.mutation.graphql b/fixtures/lib/gitlab/graphql/queries/plans.customer.mutation.graphql
new file mode 100644
index 00000000000..71ad42c3303
--- /dev/null
+++ b/fixtures/lib/gitlab/graphql/queries/plans.customer.mutation.graphql
@@ -0,0 +1,5 @@
+mutation updatePlans($tags: [PlanTag!]) {
+ plans(planTags: $tags) {
+ name
+ }
+}
diff --git a/fixtures/lib/gitlab/graphql/queries/plans.customer.query.graphql b/fixtures/lib/gitlab/graphql/queries/plans.customer.query.graphql
new file mode 100644
index 00000000000..cbc705ee81b
--- /dev/null
+++ b/fixtures/lib/gitlab/graphql/queries/plans.customer.query.graphql
@@ -0,0 +1,5 @@
+query getPlans($tags: [PlanTag!]) {
+ plans(planTags: $tags) {
+ name
+ }
+}
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 1387aeed2e7..92018adadb1 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -592,18 +592,26 @@ module API
def project_finder_params_ce
finder_params = project_finder_params_visibility_ce
+
+ finder_params.merge!(
+ params
+ .slice(:search,
+ :custom_attributes,
+ :last_activity_after,
+ :last_activity_before,
+ :repository_storage)
+ .symbolize_keys
+ .compact
+ )
+
finder_params[:with_issues_enabled] = true if params[:with_issues_enabled].present?
finder_params[:with_merge_requests_enabled] = true if params[:with_merge_requests_enabled].present?
finder_params[:without_deleted] = true
- finder_params[:search] = params[:search] if params[:search]
finder_params[:search_namespaces] = true if params[:search_namespaces].present?
finder_params[:user] = params.delete(:user) if params[:user]
- finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes]
finder_params[:id_after] = sanitize_id_param(params[:id_after]) if params[:id_after]
finder_params[:id_before] = sanitize_id_param(params[:id_before]) if params[:id_before]
- finder_params[:last_activity_after] = params[:last_activity_after] if params[:last_activity_after]
- finder_params[:last_activity_before] = params[:last_activity_before] if params[:last_activity_before]
- finder_params[:repository_storage] = params[:repository_storage] if params[:repository_storage]
+ finder_params[:tag] = params[:topic] if params[:topic].present?
finder_params
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 491afe3b9a3..27c9ea5e0cb 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -117,6 +117,7 @@ module API
optional :last_activity_after, type: DateTime, desc: 'Limit results to projects with last_activity after specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
optional :last_activity_before, type: DateTime, desc: 'Limit results to projects with last_activity before specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins'
+ optional :topic, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of topics. Limit results to projects having all topics'
use :optional_filter_params_ee
end
diff --git a/lib/gitlab/graphql/queries.rb b/lib/gitlab/graphql/queries.rb
index 74f55abccbc..5d3a9245427 100644
--- a/lib/gitlab/graphql/queries.rb
+++ b/lib/gitlab/graphql/queries.rb
@@ -264,7 +264,7 @@ module Gitlab
definitions = []
::Find.find(root.to_s) do |path|
- definitions << Definition.new(path, fragments) if query?(path)
+ definitions << Definition.new(path, fragments) if query_for_gitlab_schema?(path)
end
definitions
@@ -288,10 +288,11 @@ module Gitlab
@known_failures.fetch('filenames', []).any? { |known_failure| path.to_s.ends_with?(known_failure) }
end
- def self.query?(path)
+ def self.query_for_gitlab_schema?(path)
path.ends_with?('.graphql') &&
!path.ends_with?('.fragment.graphql') &&
- !path.ends_with?('typedefs.graphql')
+ !path.ends_with?('typedefs.graphql') &&
+ !/.*\.customer\.(query|mutation)\.graphql$/.match?(path)
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 865a6413e20..a21f161df98 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4265,6 +4265,9 @@ msgstr ""
msgid "Are you sure you want to remove the license?"
msgstr ""
+msgid "Are you sure you want to remove this deploy key? If anything is still using this key, it will stop working."
+msgstr ""
+
msgid "Are you sure you want to remove this identity?"
msgstr ""
@@ -10816,9 +10819,6 @@ msgstr ""
msgid "DeployKeys|Read access only"
msgstr ""
-msgid "DeployKeys|You are going to remove this deploy key. Are you sure?"
-msgstr ""
-
msgid "DeployTokens|Active Deploy Tokens (%{active_tokens})"
msgstr ""
@@ -11483,6 +11483,9 @@ msgstr ""
msgid "Do not display offers from third parties within GitLab"
msgstr ""
+msgid "Do you want to remove this deploy key?"
+msgstr ""
+
msgid "Dockerfile"
msgstr ""
@@ -26466,6 +26469,9 @@ msgstr ""
msgid "Remove child epic from an epic"
msgstr ""
+msgid "Remove deploy key"
+msgstr ""
+
msgid "Remove description history"
msgstr ""
diff --git a/package.json b/package.json
index f16ece96f19..5c86a0c7839 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "1.189.0",
"@gitlab/tributejs": "1.0.0",
- "@gitlab/ui": "29.7.3",
+ "@gitlab/ui": "29.8.1",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-4",
"@rails/ujs": "^6.0.3-4",
@@ -70,14 +70,14 @@
"axios": "^0.20.0",
"babel-loader": "^8.2.2",
"babel-plugin-lodash": "^3.3.4",
- "bootstrap": "4.4.1",
+ "bootstrap": "4.5.3",
"cache-loader": "^4.1.0",
"clipboard": "^1.7.1",
"codemirror": "^5.48.4",
"codesandbox-api": "0.0.23",
"compression-webpack-plugin": "^5.0.2",
"copy-webpack-plugin": "^5.1.2",
- "core-js": "^3.10.2",
+ "core-js": "^3.11.0",
"cron-validator": "^1.1.1",
"cropper": "^2.3.0",
"css-loader": "^2.1.1",
diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb
index 6218578cac6..5a36290214d 100644
--- a/spec/features/projects/deploy_keys_spec.rb
+++ b/spec/features/projects/deploy_keys_spec.rb
@@ -22,7 +22,8 @@ RSpec.describe 'Project deploy keys', :js do
page.within(find('.qa-deploy-keys-settings')) do
expect(page).to have_selector('.deploy-key', count: 1)
- accept_confirm { find('[data-testid="remove-icon"]').click }
+ click_button 'Remove'
+ click_button 'Remove deploy key'
wait_for_requests
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 0ffb6d14cb3..eafac1c4e68 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -457,20 +457,20 @@ RSpec.describe 'Pipelines', :js do
visit_project_pipelines
end
- it 'has artifacts' do
- expect(page).to have_selector('.build-artifacts')
+ it 'has artifacts dropdown' do
+ expect(page).to have_selector('[data-testid="pipeline-multi-actions-dropdown"]')
end
it 'has artifacts download dropdown' do
- find('.js-pipeline-dropdown-download').click
+ find('[data-testid="pipeline-multi-actions-dropdown"]').click
expect(page).to have_link(with_artifacts.file_type)
end
it 'has download attribute on download links' do
- find('.js-pipeline-dropdown-download').click
+ find('[data-testid="pipeline-multi-actions-dropdown"]').click
expect(page).to have_selector('a', text: 'Download')
- page.all('.build-artifacts a', text: 'Download').each do |link|
+ page.all('[data-testid="artifact-item"]', text: 'Download').each do |link|
expect(link[:download]).to eq ''
end
end
@@ -488,7 +488,7 @@ RSpec.describe 'Pipelines', :js do
visit_project_pipelines
end
- it { expect(page).not_to have_selector('.build-artifacts') }
+ it { expect(page).not_to have_selector('[data-testid="artifact-item"]') }
end
context 'without artifacts' do
@@ -503,7 +503,7 @@ RSpec.describe 'Pipelines', :js do
visit_project_pipelines
end
- it { expect(page).not_to have_selector('.build-artifacts') }
+ it { expect(page).not_to have_selector('[data-testid="artifact-item"]') }
end
context 'with trace artifact' do
@@ -514,7 +514,7 @@ RSpec.describe 'Pipelines', :js do
end
it 'does not show trace artifact as artifacts' do
- expect(page).not_to have_selector('.build-artifacts')
+ expect(page).not_to have_selector('[data-testid="artifact-item"]')
end
end
end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index ea26fe502a5..f420a8a76b9 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -117,7 +117,8 @@ RSpec.describe 'Projects > Settings > Repository settings' do
project.deploy_keys << private_deploy_key
visit project_settings_repository_path(project)
- accept_confirm { find('.deploy-key', text: private_deploy_key.title).find('[data-testid="remove-icon"]').click }
+ click_button 'Remove'
+ click_button 'Remove deploy key'
expect(page).not_to have_content(private_deploy_key.title)
end
diff --git a/spec/frontend/__helpers__/mock_apollo_helper.js b/spec/frontend/__helpers__/mock_apollo_helper.js
index bd97a06071a..520d6c72541 100644
--- a/spec/frontend/__helpers__/mock_apollo_helper.js
+++ b/spec/frontend/__helpers__/mock_apollo_helper.js
@@ -1,5 +1,5 @@
import { InMemoryCache } from 'apollo-cache-inmemory';
-import { createMockClient } from 'mock-apollo-client';
+import { createMockClient as createMockApolloClient } from 'mock-apollo-client';
import VueApollo from 'vue-apollo';
const defaultCacheOptions = {
@@ -7,13 +7,13 @@ const defaultCacheOptions = {
addTypename: false,
};
-export default (handlers = [], resolvers = {}, cacheOptions = {}) => {
+export function createMockClient(handlers = [], resolvers = {}, cacheOptions = {}) {
const cache = new InMemoryCache({
...defaultCacheOptions,
...cacheOptions,
});
- const mockClient = createMockClient({ cache, resolvers });
+ const mockClient = createMockApolloClient({ cache, resolvers });
if (Array.isArray(handlers)) {
handlers.forEach(([query, value]) => mockClient.setRequestHandler(query, value));
@@ -21,7 +21,12 @@ export default (handlers = [], resolvers = {}, cacheOptions = {}) => {
throw new Error('You should pass an array of handlers to mock Apollo client');
}
+ return mockClient;
+}
+
+export default function createMockApollo(handlers, resolvers, cacheOptions) {
+ const mockClient = createMockClient(handlers, resolvers, cacheOptions);
const apolloProvider = new VueApollo({ defaultClient: mockClient });
return apolloProvider;
-};
+}
diff --git a/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap b/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap
index 9a89e3430b4..35c02911e27 100644
--- a/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap
+++ b/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`content_editor/components/toolbar_button displays tertiary, small button with a provided label and icon 1`] = `
-"<b-button-stub event=\\"click\\" routertag=\\"a\\" size=\\"sm\\" variant=\\"default\\" type=\\"button\\" tag=\\"button\\" aria-label=\\"Bold\\" title=\\"Bold\\" class=\\"gl-mx-2 gl-button btn-default-tertiary btn-icon\\">
+"<b-button-stub size=\\"sm\\" variant=\\"default\\" type=\\"button\\" tag=\\"button\\" aria-label=\\"Bold\\" title=\\"Bold\\" class=\\"gl-mx-2 gl-button btn-default-tertiary btn-icon\\">
<!---->
<gl-icon-stub name=\\"bold\\" size=\\"16\\" class=\\"gl-button-icon\\"></gl-icon-stub>
<!---->
diff --git a/spec/frontend/deploy_keys/components/action_btn_spec.js b/spec/frontend/deploy_keys/components/action_btn_spec.js
index 21281ff15b1..307a0b6d8b0 100644
--- a/spec/frontend/deploy_keys/components/action_btn_spec.js
+++ b/spec/frontend/deploy_keys/components/action_btn_spec.js
@@ -1,4 +1,4 @@
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import actionBtn from '~/deploy_keys/components/action_btn.vue';
import eventHub from '~/deploy_keys/eventhub';
@@ -8,13 +8,16 @@ describe('Deploy keys action btn', () => {
const deployKey = data.enabled_keys[0];
let wrapper;
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findButton = () => wrapper.findComponent(GlButton);
beforeEach(() => {
wrapper = shallowMount(actionBtn, {
propsData: {
deployKey,
type: 'enable',
+ category: 'primary',
+ variant: 'confirm',
+ icon: 'edit',
},
slots: {
default: 'Enable',
@@ -26,10 +29,18 @@ describe('Deploy keys action btn', () => {
expect(wrapper.text()).toBe('Enable');
});
+ it('passes the button props on', () => {
+ expect(findButton().props()).toMatchObject({
+ category: 'primary',
+ variant: 'confirm',
+ icon: 'edit',
+ });
+ });
+
it('sends eventHub event with btn type', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- wrapper.trigger('click');
+ findButton().vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('enable.key', deployKey, expect.anything());
@@ -37,18 +48,10 @@ describe('Deploy keys action btn', () => {
});
it('shows loading spinner after click', () => {
- wrapper.trigger('click');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(findLoadingIcon().exists()).toBe(true);
- });
- });
-
- it('disables button after click', () => {
- wrapper.trigger('click');
+ findButton().vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.attributes('disabled')).toBe('disabled');
+ expect(findButton().props('loading')).toBe(true);
});
});
});
diff --git a/spec/frontend/deploy_keys/components/app_spec.js b/spec/frontend/deploy_keys/components/app_spec.js
index b48e0424580..a72b2b00776 100644
--- a/spec/frontend/deploy_keys/components/app_spec.js
+++ b/spec/frontend/deploy_keys/components/app_spec.js
@@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'spec/test_constants';
import deployKeysApp from '~/deploy_keys/components/app.vue';
+import ConfirmModal from '~/deploy_keys/components/confirm_modal.vue';
import eventHub from '~/deploy_keys/eventhub';
import axios from '~/lib/utils/axios_utils';
@@ -36,6 +37,7 @@ describe('Deploy keys app component', () => {
const findLoadingIcon = () => wrapper.find('.gl-spinner');
const findKeyPanels = () => wrapper.findAll('.deploy-keys .gl-tabs-nav li');
+ const findModal = () => wrapper.findComponent(ConfirmModal);
it('renders loading icon while waiting for request', () => {
mock.onGet(TEST_ENDPOINT).reply(() => new Promise());
@@ -94,11 +96,16 @@ describe('Deploy keys app component', () => {
const key = data.public_keys[0];
return mountComponent()
.then(() => {
- jest.spyOn(window, 'confirm').mockReturnValue(true);
jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve());
- eventHub.$emit('disable.key', key);
+ eventHub.$emit('disable.key', key, () => {});
+
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(findModal().props('visible')).toBe(true);
+ findModal().vm.$emit('remove');
return wrapper.vm.$nextTick();
})
@@ -112,11 +119,16 @@ describe('Deploy keys app component', () => {
const key = data.public_keys[0];
return mountComponent()
.then(() => {
- jest.spyOn(window, 'confirm').mockReturnValue(true);
jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve());
- eventHub.$emit('remove.key', key);
+ eventHub.$emit('remove.key', key, () => {});
+
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(findModal().props('visible')).toBe(true);
+ findModal().vm.$emit('remove');
return wrapper.vm.$nextTick();
})
diff --git a/spec/frontend/deploy_keys/components/confirm_modal_spec.js b/spec/frontend/deploy_keys/components/confirm_modal_spec.js
new file mode 100644
index 00000000000..42cc2b377a7
--- /dev/null
+++ b/spec/frontend/deploy_keys/components/confirm_modal_spec.js
@@ -0,0 +1,28 @@
+import { GlModal } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import ConfirmModal from '~/deploy_keys/components/confirm_modal.vue';
+
+describe('~/deploy_keys/components/confirm_modal.vue', () => {
+ let wrapper;
+ let modal;
+
+ beforeEach(() => {
+ wrapper = mount(ConfirmModal, { propsData: { modalId: 'test', visible: true } });
+ modal = extendedWrapper(wrapper.findComponent(GlModal));
+ });
+
+ it('emits a remove event if the primary button is clicked', () => {
+ modal.findByText('Remove deploy key').trigger('click');
+ expect(wrapper.emitted('remove')).toEqual([[]]);
+ });
+
+ it('emits a cancel event if the secondary button is clicked', () => {
+ modal.findByText('Cancel').trigger('click');
+ expect(wrapper.emitted('cancel')).toEqual([[]]);
+ });
+
+ it('displays the warning about removing the deploy key', () => {
+ expect(modal.text()).toContain('Are you sure you want to remove this deploy key?');
+ });
+});
diff --git a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
index bea27c8877d..9f49cb4007a 100644
--- a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
+++ b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
@@ -24,7 +24,9 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] =
role="columnheader"
scope="col"
>
- Jira display name
+ <div>
+ Jira display name
+ </div>
</th>
<th
aria-colindex="2"
@@ -32,14 +34,18 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] =
class=""
role="columnheader"
scope="col"
- />
+ >
+ <div />
+ </th>
<th
aria-colindex="3"
class=""
role="columnheader"
scope="col"
>
- GitLab username
+ <div>
+ GitLab username
+ </div>
</th>
</tr>
</thead>
diff --git a/spec/frontend/pipelines/pipeline_multi_actions_spec.js b/spec/frontend/pipelines/pipeline_multi_actions_spec.js
new file mode 100644
index 00000000000..bb110a8924f
--- /dev/null
+++ b/spec/frontend/pipelines/pipeline_multi_actions_spec.js
@@ -0,0 +1,67 @@
+import { GlDropdown, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import PipelineMultiActions from '~/pipelines/components/pipelines_list/pipeline_multi_actions.vue';
+
+describe('Pipeline Multi Actions Dropdown', () => {
+ let wrapper;
+
+ const artifactItemTestId = 'artifact-item';
+
+ const defaultProps = {
+ artifacts: [
+ {
+ name: 'job my-artifact',
+ path: '/download/path',
+ },
+ {
+ name: 'job-2 my-artifact-2',
+ path: '/download/path-two',
+ },
+ ],
+ };
+
+ const createComponent = (props = defaultProps) => {
+ wrapper = extendedWrapper(
+ shallowMount(PipelineMultiActions, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ stubs: {
+ GlSprintf,
+ },
+ }),
+ );
+ };
+
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findAllArtifactItems = () => wrapper.findAllByTestId(artifactItemTestId);
+ const findFirstArtifactItem = () => wrapper.findByTestId(artifactItemTestId);
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render the dropdown', () => {
+ expect(findDropdown().exists()).toBe(true);
+ });
+
+ describe('Artifacts', () => {
+ it('should render all the provided artifacts', () => {
+ expect(findAllArtifactItems()).toHaveLength(defaultProps.artifacts.length);
+ });
+
+ it('should render the correct artifact name and path', () => {
+ expect(findFirstArtifactItem().attributes('href')).toBe(defaultProps.artifacts[0].path);
+
+ expect(findFirstArtifactItem().text()).toBe(
+ `Download ${defaultProps.artifacts[0].name} artifact`,
+ );
+ });
+ });
+});
diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb
index 787e2174070..832295db005 100644
--- a/spec/graphql/types/ci/job_type_spec.rb
+++ b/spec/graphql/types/ci/job_type_spec.rb
@@ -15,10 +15,12 @@ RSpec.describe Types::Ci::JobType do
commitPath
coverage
created_at
+ created_by_tag
detailedStatus
duration
finished_at
id
+ manual_job
name
needs
pipeline
@@ -34,6 +36,7 @@ RSpec.describe Types::Ci::JobType do
started_at
status
tags
+ triggered
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index 120dbe7cb49..c4366cffd7f 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -409,4 +409,33 @@ RSpec.describe AvatarsHelper do
end
end
end
+
+ describe '#avatar_without_link' do
+ let(:options) { { size: 32 } }
+
+ subject { helper.avatar_without_link(resource, options) }
+
+ context 'with users' do
+ let(:resource) { user }
+
+ it 'displays user avatar' do
+ is_expected.to eq tag(
+ :img,
+ alt: "#{user.name}'s avatar",
+ src: avatar_icon_for_user(user, 32),
+ data: { container: 'body' },
+ class: 'avatar s32 has-tooltip',
+ title: user.name
+ )
+ end
+ end
+
+ context 'with groups' do
+ let(:resource) { build_stubbed(:group, name: 'foo') }
+
+ it 'displays group avatar' do
+ is_expected.to match(%r{<div class="avatar identicon bg\d+ s32">F</div>})
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/graphql/queries_spec.rb b/spec/lib/gitlab/graphql/queries_spec.rb
index a140a283c1b..a1cd2cdb2de 100644
--- a/spec/lib/gitlab/graphql/queries_spec.rb
+++ b/spec/lib/gitlab/graphql/queries_spec.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require 'spec_helper'
require 'fast_spec_helper'
require "test_prof/recipes/rspec/let_it_be"
@@ -124,6 +125,18 @@ RSpec.describe Gitlab::Graphql::Queries do
expect(described_class.find(path)).to be_empty
end
+ it 'ignores customer.query.graphql' do
+ path = root / 'plans.customer.query.graphql'
+
+ expect(described_class.find(path)).to be_empty
+ end
+
+ it 'ignores customer.mutation.graphql' do
+ path = root / 'plans.customer.mutation.graphql'
+
+ expect(described_class.find(path)).to be_empty
+ end
+
it 'finds all query definitions under a root directory' do
found = described_class.find(root)
@@ -137,7 +150,9 @@ RSpec.describe Gitlab::Graphql::Queries do
expect(found).not_to include(
definition_of(root / 'typedefs.graphql'),
- definition_of(root / 'author.fragment.graphql')
+ definition_of(root / 'author.fragment.graphql'),
+ definition_of(root / 'plans.customer.query.graphql'),
+ definition_of(root / 'plans.customer.mutation.graphql')
)
end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index d8eb4ebc432..5d2cfcb7611 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -212,6 +212,26 @@ RSpec.describe Service do
end
end
+ describe '#group_level?' do
+ it 'is true when service has a group' do
+ expect(build(:service, group: group)).to be_group_level
+ end
+
+ it 'is false when service has no group' do
+ expect(build(:service, group: nil)).not_to be_group_level
+ end
+ end
+
+ describe '#instance_level?' do
+ it 'is true when service has instance-level integration' do
+ expect(build(:service, :instance)).to be_instance_level
+ end
+
+ it 'is false when service does not have instance-level integration' do
+ expect(build(:service, instance: false)).not_to be_instance_level
+ end
+ end
+
describe '.find_or_initialize_non_project_specific_integration' do
let!(:service1) { create(:jira_service, project_id: nil, group_id: group.id) }
let!(:service2) { create(:jira_service) }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index b0ecb711283..a368d66ab11 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -223,6 +223,52 @@ RSpec.describe API::Projects do
expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
end
+ context 'filter by topic (column tag_list)' do
+ before do
+ project.update!(tag_list: %w(ruby javascript))
+ end
+
+ it 'returns no projects' do
+ get api('/projects', user), params: { topic: 'foo' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_empty
+ end
+
+ it 'returns matching project for a single topic' do
+ get api('/projects', user), params: { topic: 'ruby' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to contain_exactly a_hash_including('id' => project.id)
+ end
+
+ it 'returns matching project for multiple topics' do
+ get api('/projects', user), params: { topic: 'ruby, javascript' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to contain_exactly a_hash_including('id' => project.id)
+ end
+
+ it 'returns no projects if project match only some topic' do
+ get api('/projects', user), params: { topic: 'ruby, foo' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_empty
+ end
+
+ it 'ignores topic if it is empty' do
+ get api('/projects', user), params: { topic: '' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_present
+ end
+ end
+
context 'and with_issues_enabled=true' do
it 'only returns projects with issues enabled' do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
diff --git a/yarn.lock b/yarn.lock
index 184a5037a13..a8e560490a4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -907,14 +907,14 @@
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
-"@gitlab/ui@29.7.3":
- version "29.7.3"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.7.3.tgz#f7762cc6a20fc8f1c6403822f8cbef821f2438dd"
- integrity sha512-2pU7t+kFB4ndq3ZW8PzZ26LEj+vAd8AaJLvBmBDba1hEx5KlH4B9U30lGl+UYzgjY3yoe68eObK26vJjJ1rb0g==
+"@gitlab/ui@29.8.1":
+ version "29.8.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.8.1.tgz#9bac442e0579e7eb5610a3e08c481bb272514559"
+ integrity sha512-Pn1n4K2MR42YoQI+8i9Hs5ljtb2TQWhx1ectEsft/xMLS8TTm8okM553sM9Z7kf1coizFKILQOmz+E5RwcnOsQ==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"
- bootstrap-vue "2.14.0"
+ bootstrap-vue "2.15.0"
copy-to-clipboard "^3.0.8"
dompurify "^2.2.7"
echarts "^4.9.0"
@@ -2480,21 +2480,21 @@ bonjour@^3.5.0:
multicast-dns "^6.0.1"
multicast-dns-service-types "^1.1.0"
-bootstrap-vue@2.14.0:
- version "2.14.0"
- resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.14.0.tgz#de88b607627431980b707e6f069f13ef8cc897bd"
- integrity sha512-sqbS7iHYCZEj/dDx4Yaze99HcX6bZjO4bSWZ0xSgJwtWQlbfB2VDJ9Qjzjp9XI8TT32wYNGAMpnXpYjQvv5qyQ==
+bootstrap-vue@2.15.0:
+ version "2.15.0"
+ resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.15.0.tgz#0dfc12c054496c0f10efed510da1def41697cf3c"
+ integrity sha512-ncxWkDG0mKFVot314wWKJELi+ESO7k6ngV//qvJFs9iVzlFI8Hx3rBVbpcPW2vrJ+0vitH8N2SOwn4fdQ3frMQ==
dependencies:
"@nuxt/opencollective" "^0.3.0"
- bootstrap ">=4.4.1 <5.0.0"
+ bootstrap ">=4.5.0 <5.0.0"
popper.js "^1.16.1"
portal-vue "^2.1.7"
vue-functional-data-merge "^3.1.0"
-bootstrap@4.4.1, "bootstrap@>=4.4.1 <5.0.0":
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.4.1.tgz#8582960eea0c5cd2bede84d8b0baf3789c3e8b01"
- integrity sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==
+bootstrap@4.5.3, "bootstrap@>=4.5.0 <5.0.0":
+ version "4.5.3"
+ resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.3.tgz#c6a72b355aaf323920be800246a6e4ef30997fe6"
+ integrity sha512-o9ppKQioXGqhw8Z7mah6KdTYpNQY//tipnkxppWhPbiSWdD+1raYsnhwEZjkTHYbGee4cVQ0Rx65EhOY/HNLcQ==
boxen@^4.2.0:
version "4.2.0"
@@ -3387,10 +3387,10 @@ core-js-pure@^3.0.0:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
-core-js@^3.1.3, core-js@^3.10.2:
- version "3.10.2"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.10.2.tgz#17cb038ce084522a717d873b63f2b3ee532e2cd5"
- integrity sha512-W+2oVYeNghuBr3yTzZFQ5rfmjZtYB/Ubg87R5YOmlGrIb+Uw9f7qjUbhsj+/EkXhcV7eOD3jiM4+sgraX3FZUw==
+core-js@^3.1.3, core-js@^3.11.0:
+ version "3.11.0"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.11.0.tgz#05dac6aa70c0a4ad842261f8957b961d36eb8926"
+ integrity sha512-bd79DPpx+1Ilh9+30aT5O1sgpQd4Ttg8oqkqi51ZzhedMM1omD2e6IOF48Z/DzDCZ2svp49tN/3vneTK6ZBkXw==
core-js@~2.3.0:
version "2.3.0"