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:
-rw-r--r--.rubocop_manual_todo.yml22
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue11
-rw-r--r--app/assets/javascripts/boards/components/board_content_sidebar.vue13
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue2
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue6
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_issue_title.vue2
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue12
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue16
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue10
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue10
-rw-r--r--app/assets/javascripts/boards/stores/actions.js107
-rw-r--r--app/assets/javascripts/boards/stores/getters.js6
-rw-r--r--app/assets/javascripts/boards/stores/mutation_types.js10
-rw-r--r--app/assets/javascripts/boards/stores/mutations.js33
-rw-r--r--app/assets/stylesheets/page_bundles/boards.scss4
-rw-r--r--app/assets/stylesheets/pages/issuable.scss2
-rw-r--r--app/models/ci/pipeline.rb22
-rw-r--r--app/models/commit_status.rb15
-rw-r--r--app/serializers/build_artifact_entity.rb14
-rw-r--r--app/serializers/merge_requests/pipeline_entity.rb2
-rw-r--r--app/serializers/pipeline_details_entity.rb2
-rw-r--r--app/services/ci/process_pipeline_service.rb2
-rw-r--r--app/views/groups/group_members/index.html.haml8
-rw-r--r--app/views/shared/boards/components/_sidebar.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--changelogs/unreleased/21098-reducing-cancel-sql-queries.yml5
-rw-r--r--changelogs/unreleased/board-view-deprecated-button.yml5
-rw-r--r--changelogs/unreleased/btn-confirm-project-settings.yml5
-rw-r--r--changelogs/unreleased/gl-badge-members.yml5
-rw-r--r--changelogs/unreleased/id-reduce-cached-requests.yml5
-rw-r--r--changelogs/unreleased/issue-325836-fix-empty-line-after-let-it-be-groups-module.yml5
-rw-r--r--changelogs/unreleased/issue-325836-fix-empty-line-after-let-it-be-projects-controller.yml5
-rw-r--r--changelogs/unreleased/mwaw-301055-update-metrics-definitions-for-product-intelligence-group.yml5
-rw-r--r--changelogs/unreleased/use-atomic-mutations-boards.yml5
-rw-r--r--config/metrics/aggregates/common.yml16
-rw-r--r--config/metrics/counts_28d/20210216183203_product_analytics_test_metrics_union.yml27
-rw-r--r--config/metrics/counts_28d/20210216183205_product_analytics_test_metrics_intersection.yml25
-rw-r--r--config/metrics/counts_7d/20210216183213_product_analytics_test_metrics_union.yml21
-rw-r--r--config/metrics/counts_7d/20210216183215_product_analytics_test_metrics_intersection.yml21
-rw-r--r--config/metrics/schema.json2
-rw-r--r--doc/administration/job_artifacts.md19
-rw-r--r--doc/development/usage_ping/dictionary.md36
-rw-r--r--doc/development/usage_ping/metrics_dictionary.md3
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb7
-rw-r--r--lib/gitlab/ci/pipeline/chain/helpers.rb15
-rw-r--r--lib/gitlab/ci/pipeline/chain/metrics.rb2
-rw-r--r--lib/gitlab/ci/pipeline/metrics.rb30
-rw-r--r--locale/gitlab.pot3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb7
-rw-r--r--spec/controllers/projects/alerting/notifications_controller_spec.rb2
-rw-r--r--spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb1
-rw-r--r--spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb1
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb1
-rw-r--r--spec/controllers/projects/feature_flags_controller_spec.rb1
-rw-r--r--spec/controllers/projects/incidents_controller_spec.rb1
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb5
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb1
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb1
-rw-r--r--spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb1
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb1
-rw-r--r--spec/controllers/projects/pipelines_settings_controller_spec.rb1
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb1
-rw-r--r--spec/controllers/projects/releases/evidences_controller_spec.rb1
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb1
-rw-r--r--spec/controllers/projects/settings/ci_cd_controller_spec.rb1
-rw-r--r--spec/controllers/projects/settings/operations_controller_spec.rb1
-rw-r--r--spec/controllers/projects/static_site_editor_controller_spec.rb1
-rw-r--r--spec/controllers/projects/todos_controller_spec.rb1
-rw-r--r--spec/controllers/projects_controller_spec.rb3
-rw-r--r--spec/features/boards/new_issue_spec.rb4
-rw-r--r--spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb1
-rw-r--r--spec/features/groups_spec.rb2
-rw-r--r--spec/frontend/boards/components/board_content_sidebar_spec.js2
-rw-r--r--spec/frontend/boards/components/board_form_spec.js2
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js14
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js10
-rw-r--r--spec/frontend/boards/stores/actions_spec.js215
-rw-r--r--spec/frontend/boards/stores/getters_spec.js14
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js175
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb13
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb5
-rw-r--r--spec/models/ci/pipeline_spec.rb41
-rw-r--r--spec/models/commit_status_spec.rb17
-rw-r--r--spec/serializers/build_artifact_entity_spec.rb18
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb19
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb52
-rw-r--r--spec/support/shared_examples/features/sidebar_shared_examples.rb12
89 files changed, 832 insertions, 448 deletions
diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml
index 8a09d3eb4b0..394bbc2f633 100644
--- a/.rubocop_manual_todo.yml
+++ b/.rubocop_manual_todo.yml
@@ -804,26 +804,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- spec/controllers/dashboard/projects_controller_spec.rb
- spec/controllers/invites_controller_spec.rb
- spec/controllers/profiles/emails_controller_spec.rb
- - spec/controllers/projects/alerting/notifications_controller_spec.rb
- - spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb
- - spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb
- - spec/controllers/projects/environments_controller_spec.rb
- - spec/controllers/projects/feature_flags_controller_spec.rb
- - spec/controllers/projects/incidents_controller_spec.rb
- - spec/controllers/projects/issues_controller_spec.rb
- - spec/controllers/projects/jobs_controller_spec.rb
- - spec/controllers/projects/merge_requests_controller_spec.rb
- - spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb
- - spec/controllers/projects/pipelines_controller_spec.rb
- - spec/controllers/projects/pipelines_settings_controller_spec.rb
- - spec/controllers/projects/raw_controller_spec.rb
- - spec/controllers/projects/releases/evidences_controller_spec.rb
- - spec/controllers/projects/releases_controller_spec.rb
- - spec/controllers/projects/settings/ci_cd_controller_spec.rb
- - spec/controllers/projects/settings/operations_controller_spec.rb
- - spec/controllers/projects/static_site_editor_controller_spec.rb
- - spec/controllers/projects/todos_controller_spec.rb
- - spec/controllers/projects_controller_spec.rb
- spec/controllers/users/terms_controller_spec.rb
- spec/features/boards/multiple_boards_spec.rb
- spec/features/boards/sidebar_assignee_spec.rb
@@ -842,8 +822,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- spec/features/file_uploads/group_import_spec.rb
- spec/features/file_uploads/project_import_spec.rb
- spec/features/file_uploads/user_avatar_spec.rb
- - spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb
- - spec/features/groups_spec.rb
- spec/features/issues/user_sees_breadcrumb_links_spec.rb
- spec/features/markdown/metrics_spec.rb
- spec/features/merge_request/user_creates_merge_request_spec.rb
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 9a8bd7a1eb2..c2ce5930415 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-22c9b23bfcdeedc373462a54cea5ee55790a62b1
+408a10ac19698a289f4ee1e2a44ddaba31630112
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index 9639ca95cca..a4b1e6adacf 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -18,6 +18,8 @@ export default {
? BoardColumn
: BoardColumnDeprecated,
BoardContentSidebar: () => import('~/boards/components/board_content_sidebar.vue'),
+ EpicBoardContentSidebar: () =>
+ import('ee_component/boards/components/epic_board_content_sidebar.vue'),
EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
GlAlert,
},
@@ -133,7 +135,14 @@ export default {
<board-content-sidebar
v-if="isSwimlanesOn || glFeatures.graphqlBoardLists"
- class="issue-boards-sidebar"
+ class="boards-sidebar"
+ data-testid="issue-boards-sidebar"
+ />
+
+ <epic-board-content-sidebar
+ v-else-if="isEpicBoard"
+ class="boards-sidebar"
+ data-testid="epic-boards-sidebar"
/>
</div>
</template>
diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue
index d74057e99bc..21b14124f59 100644
--- a/app/assets/javascripts/boards/components/board_content_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue
@@ -34,7 +34,7 @@ export default {
computed: {
...mapGetters([
'isSidebarOpen',
- 'activeIssue',
+ 'activeBoardItem',
'groupPathForActiveIssue',
'projectPathForActiveIssue',
]),
@@ -46,13 +46,13 @@ export default {
return this.isIssuableSidebar && this.isSidebarOpen;
},
fullPath() {
- return this.activeIssue?.referencePath?.split('#')[0] || '';
+ return this.activeBoardItem?.referencePath?.split('#')[0] || '';
},
},
methods: {
...mapActions(['toggleBoardItem', 'setAssignees']),
handleClose() {
- this.toggleBoardItem({ boardItem: this.activeIssue, sidebarType: this.sidebarType });
+ this.toggleBoardItem({ boardItem: this.activeBoardItem, sidebarType: this.sidebarType });
},
},
};
@@ -61,7 +61,6 @@ export default {
<template>
<gl-drawer
v-if="showSidebar"
- data-testid="sidebar-drawer"
:open="isSidebarOpen"
:header-height="$options.headerHeight"
@close="handleClose"
@@ -70,9 +69,9 @@ export default {
<template #default>
<board-sidebar-issue-title />
<sidebar-assignees-widget
- :iid="activeIssue.iid"
+ :iid="activeBoardItem.iid"
:full-path="fullPath"
- :initial-assignees="activeIssue.assignees"
+ :initial-assignees="activeBoardItem.assignees"
class="assignee"
@assignees-updated="setAssignees"
/>
@@ -80,7 +79,7 @@ export default {
<div>
<board-sidebar-milestone-select />
<sidebar-iteration-widget
- :iid="activeIssue.iid"
+ :iid="activeBoardItem.iid"
:workspace-path="projectPathForActiveIssue"
:iterations-workspace-path="groupPathForActiveIssue"
:issuable-type="issuableType"
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index e5eec70c544..78da4137d69 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -127,7 +127,7 @@ export default {
if (this.isDeleteForm) {
return 'danger';
}
- return 'info';
+ return 'confirm';
},
title() {
if (this.readonly) {
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue
index c6836efc32d..13e1e232676 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue
@@ -18,16 +18,16 @@ export default {
};
},
computed: {
- ...mapGetters(['activeIssue', 'projectPathForActiveIssue']),
+ ...mapGetters(['activeBoardItem', 'projectPathForActiveIssue']),
hasDueDate() {
- return this.activeIssue.dueDate != null;
+ return this.activeBoardItem.dueDate != null;
},
parsedDueDate() {
if (!this.hasDueDate) {
return null;
}
- return parsePikadayDate(this.activeIssue.dueDate);
+ return parsePikadayDate(this.activeBoardItem.dueDate);
},
formattedDueDate() {
if (!this.hasDueDate) {
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_issue_title.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_issue_title.vue
index 95864bd62a7..0aa65a30b46 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_issue_title.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_issue_title.vue
@@ -27,7 +27,7 @@ export default {
};
},
computed: {
- ...mapGetters({ issue: 'activeIssue' }),
+ ...mapGetters({ issue: 'activeBoardItem' }),
pendingChangesStorageKey() {
return this.getPendingChangesKey(this.issue);
},
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
index 55b1596ee18..f78be83cd82 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
@@ -21,9 +21,9 @@ export default {
};
},
computed: {
- ...mapGetters(['activeIssue', 'projectPathForActiveIssue']),
+ ...mapGetters(['activeBoardItem', 'projectPathForActiveIssue']),
selectedLabels() {
- const { labels = [] } = this.activeIssue;
+ const { labels = [] } = this.activeBoardItem;
return labels.map((label) => ({
...label,
@@ -31,7 +31,7 @@ export default {
}));
},
issueLabels() {
- const { labels = [] } = this.activeIssue;
+ const { labels = [] } = this.activeBoardItem;
return labels.map((label) => ({
...label,
@@ -40,7 +40,7 @@ export default {
},
},
methods: {
- ...mapActions(['setActiveIssueLabels']),
+ ...mapActions(['setActiveBoardItemLabels']),
async setLabels(payload) {
this.loading = true;
this.$refs.sidebarItem.collapse();
@@ -52,7 +52,7 @@ export default {
.map((label) => label.id);
const input = { addLabelIds, removeLabelIds, projectPath: this.projectPathForActiveIssue };
- await this.setActiveIssueLabels(input);
+ await this.setActiveBoardItemLabels(input);
} catch (e) {
createFlash({ message: __('An error occurred while updating labels.') });
} finally {
@@ -65,7 +65,7 @@ export default {
try {
const removeLabelIds = [getIdFromGraphQLId(id)];
const input = { removeLabelIds, projectPath: this.projectPathForActiveIssue };
- await this.setActiveIssueLabels(input);
+ await this.setActiveBoardItemLabels(input);
} catch (e) {
createFlash({ message: __('An error occurred when removing the label.') });
} finally {
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue
index cce3172d985..ad225c7bf5c 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue
@@ -56,20 +56,20 @@ export default {
},
},
computed: {
- ...mapGetters(['activeIssue']),
+ ...mapGetters(['activeBoardItem']),
hasMilestone() {
- return this.activeIssue.milestone !== null;
+ return this.activeBoardItem.milestone !== null;
},
groupFullPath() {
- const { referencePath = '' } = this.activeIssue;
+ const { referencePath = '' } = this.activeBoardItem;
return referencePath.slice(0, referencePath.indexOf('/'));
},
projectPath() {
- const { referencePath = '' } = this.activeIssue;
+ const { referencePath = '' } = this.activeBoardItem;
return referencePath.slice(0, referencePath.indexOf('#'));
},
dropdownText() {
- return this.activeIssue.milestone?.title ?? this.$options.i18n.noMilestone;
+ return this.activeBoardItem.milestone?.title ?? this.$options.i18n.noMilestone;
},
},
methods: {
@@ -118,7 +118,7 @@ export default {
@close="handleClose"
>
<template v-if="hasMilestone" #collapsed>
- <strong class="gl-text-gray-900">{{ activeIssue.milestone.title }}</strong>
+ <strong class="gl-text-gray-900">{{ activeBoardItem.milestone.title }}</strong>
</template>
<gl-dropdown
ref="dropdown"
@@ -131,7 +131,7 @@ export default {
<gl-dropdown-item
data-testid="no-milestone-item"
:is-check-item="true"
- :is-checked="!activeIssue.milestone"
+ :is-checked="!activeBoardItem.milestone"
@click="setMilestone(null)"
>
{{ $options.i18n.noMilestone }}
@@ -143,7 +143,7 @@ export default {
v-for="milestone in milestones"
:key="milestone.id"
:is-check-item="true"
- :is-checked="activeIssue.milestone && milestone.id === activeIssue.milestone.id"
+ :is-checked="activeBoardItem.milestone && milestone.id === activeBoardItem.milestone.id"
data-testid="milestone-item"
@click="setMilestone(milestone.id)"
>
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
index f01c8e8fa20..8a7d0671fb3 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
@@ -27,9 +27,9 @@ export default {
};
},
computed: {
- ...mapGetters(['activeIssue', 'projectPathForActiveIssue']),
+ ...mapGetters(['activeBoardItem', 'projectPathForActiveIssue']),
notificationText() {
- return this.activeIssue.emailsDisabled
+ return this.activeBoardItem.emailsDisabled
? this.$options.i18n.header.subscribeDisabledDescription
: this.$options.i18n.header.title;
},
@@ -41,7 +41,7 @@ export default {
try {
await this.setActiveIssueSubscribed({
- subscribed: !this.activeIssue.subscribed,
+ subscribed: !this.activeBoardItem.subscribed,
projectPath: this.projectPathForActiveIssue,
});
} catch (error) {
@@ -61,8 +61,8 @@ export default {
>
<span data-testid="notification-header-text"> {{ notificationText }} </span>
<gl-toggle
- v-if="!activeIssue.emailsDisabled"
- :value="activeIssue.subscribed"
+ v-if="!activeBoardItem.emailsDisabled"
+ :value="activeBoardItem.subscribed"
:is-loading="loading"
:label="$options.i18n.header.title"
label-position="hidden"
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue
index f853b744d23..96d444980a8 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue
@@ -8,17 +8,17 @@ export default {
},
inject: ['timeTrackingLimitToHours'],
computed: {
- ...mapGetters(['activeIssue']),
+ ...mapGetters(['activeBoardItem']),
},
};
</script>
<template>
<issuable-time-tracker
- :time-estimate="activeIssue.timeEstimate"
- :time-spent="activeIssue.totalTimeSpent"
- :human-time-estimate="activeIssue.humanTimeEstimate"
- :human-time-spent="activeIssue.humanTotalTimeSpent"
+ :time-estimate="activeBoardItem.timeEstimate"
+ :time-spent="activeBoardItem.totalTimeSpent"
+ :human-time-estimate="activeBoardItem.humanTimeEstimate"
+ :human-time-spent="activeBoardItem.humanTotalTimeSpent"
:limit-to-hours="timeTrackingLimitToHours"
:show-collapsed="false"
/>
diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js
index 4763902ce86..9211406d8b2 100644
--- a/app/assets/javascripts/boards/stores/actions.js
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -12,6 +12,7 @@ import {
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { convertObjectPropsToCamelCase, urlParamsToObject } from '~/lib/utils/common_utils';
+import { s__ } from '~/locale';
import {
formatBoardLists,
formatListIssues,
@@ -371,20 +372,20 @@ export default {
},
setAssignees: ({ commit, getters }, assigneeUsernames) => {
- commit('UPDATE_ISSUE_BY_ID', {
- issueId: getters.activeIssue.id,
+ commit('UPDATE_BOARD_ITEM_BY_ID', {
+ itemId: getters.activeBoardItem.id,
prop: 'assignees',
value: assigneeUsernames,
});
},
setActiveIssueMilestone: async ({ commit, getters }, input) => {
- const { activeIssue } = getters;
+ const { activeBoardItem } = getters;
const { data } = await gqlClient.mutate({
mutation: issueSetMilestoneMutation,
variables: {
input: {
- iid: String(activeIssue.iid),
+ iid: String(activeBoardItem.iid),
milestoneId: getIdFromGraphQLId(input.milestoneId),
projectPath: input.projectPath,
},
@@ -395,65 +396,71 @@ export default {
throw new Error(data.updateIssue.errors);
}
- commit(types.UPDATE_ISSUE_BY_ID, {
- issueId: activeIssue.id,
+ commit(types.UPDATE_BOARD_ITEM_BY_ID, {
+ itemId: activeBoardItem.id,
prop: 'milestone',
value: data.updateIssue.issue.milestone,
});
},
- createNewIssue: ({ commit, state }, issueInput) => {
- const { boardConfig } = state;
+ addListItem: ({ commit }, { list, item, position }) => {
+ commit(types.ADD_BOARD_ITEM_TO_LIST, { listId: list.id, itemId: item.id, atIndex: position });
+ commit(types.UPDATE_BOARD_ITEM, item);
+ },
+
+ removeListItem: ({ commit }, { listId, itemId }) => {
+ commit(types.REMOVE_BOARD_ITEM_FROM_LIST, { listId, itemId });
+ commit(types.REMOVE_BOARD_ITEM, itemId);
+ },
+ addListNewIssue: (
+ { state: { boardConfig, boardType, fullPath }, dispatch, commit },
+ { issueInput, list, placeholderId = `tmp-${new Date().getTime()}` },
+ ) => {
const input = formatIssueInput(issueInput, boardConfig);
- const { boardType, fullPath } = state;
if (boardType === BoardType.project) {
input.projectPath = fullPath;
}
- return gqlClient
+ const placeholderIssue = formatIssue({ ...issueInput, id: placeholderId });
+ dispatch('addListItem', { list, item: placeholderIssue, position: 0 });
+
+ gqlClient
.mutate({
mutation: issueCreateMutation,
variables: { input },
})
.then(({ data }) => {
if (data.createIssue.errors.length) {
- commit(types.CREATE_ISSUE_FAILURE);
- } else {
- return data.createIssue?.issue;
+ throw new Error();
}
- return null;
- })
- .catch(() => commit(types.CREATE_ISSUE_FAILURE));
- },
- addListIssue: ({ commit }, { list, issue, position }) => {
- commit(types.ADD_ISSUE_TO_LIST, { list, issue, position });
+ const rawIssue = data.createIssue?.issue;
+ const formattedIssue = formatIssue({ ...rawIssue, id: getIdFromGraphQLId(rawIssue.id) });
+ dispatch('removeListItem', { listId: list.id, itemId: placeholderId });
+ dispatch('addListItem', { list, item: formattedIssue, position: 0 });
+ })
+ .catch(() => {
+ dispatch('removeListItem', { listId: list.id, itemId: placeholderId });
+ commit(
+ types.SET_ERROR,
+ s__('Boards|An error occurred while creating the issue. Please try again.'),
+ );
+ });
},
- addListNewIssue: ({ commit, dispatch }, { issueInput, list }) => {
- const issue = formatIssue({ ...issueInput, id: 'tmp' });
- commit(types.ADD_ISSUE_TO_LIST, { list, issue, position: 0 });
-
- dispatch('createNewIssue', issueInput)
- .then((res) => {
- commit(types.ADD_ISSUE_TO_LIST, {
- list,
- issue: formatIssue({ ...res, id: getIdFromGraphQLId(res.id) }),
- });
- commit(types.REMOVE_ISSUE_FROM_LIST, { list, issue });
- })
- .catch(() => commit(types.ADD_ISSUE_TO_LIST_FAILURE, { list, issueId: issueInput.id }));
+ setActiveBoardItemLabels: ({ dispatch }, params) => {
+ dispatch('setActiveIssueLabels', params);
},
setActiveIssueLabels: async ({ commit, getters }, input) => {
- const { activeIssue } = getters;
+ const { activeBoardItem } = getters;
const { data } = await gqlClient.mutate({
mutation: issueSetLabelsMutation,
variables: {
input: {
- iid: String(activeIssue.iid),
+ iid: String(activeBoardItem.iid),
addLabelIds: input.addLabelIds ?? [],
removeLabelIds: input.removeLabelIds ?? [],
projectPath: input.projectPath,
@@ -465,20 +472,20 @@ export default {
throw new Error(data.updateIssue.errors);
}
- commit(types.UPDATE_ISSUE_BY_ID, {
- issueId: activeIssue.id,
+ commit(types.UPDATE_BOARD_ITEM_BY_ID, {
+ itemId: activeBoardItem.id,
prop: 'labels',
value: data.updateIssue.issue.labels.nodes,
});
},
setActiveIssueDueDate: async ({ commit, getters }, input) => {
- const { activeIssue } = getters;
+ const { activeBoardItem } = getters;
const { data } = await gqlClient.mutate({
mutation: issueSetDueDateMutation,
variables: {
input: {
- iid: String(activeIssue.iid),
+ iid: String(activeBoardItem.iid),
projectPath: input.projectPath,
dueDate: input.dueDate,
},
@@ -489,8 +496,8 @@ export default {
throw new Error(data.updateIssue.errors);
}
- commit(types.UPDATE_ISSUE_BY_ID, {
- issueId: activeIssue.id,
+ commit(types.UPDATE_BOARD_ITEM_BY_ID, {
+ itemId: activeBoardItem.id,
prop: 'dueDate',
value: data.updateIssue.issue.dueDate,
});
@@ -501,7 +508,7 @@ export default {
mutation: issueSetSubscriptionMutation,
variables: {
input: {
- iid: String(getters.activeIssue.iid),
+ iid: String(getters.activeBoardItem.iid),
projectPath: input.projectPath,
subscribedState: input.subscribed,
},
@@ -512,20 +519,20 @@ export default {
throw new Error(data.issueSetSubscription.errors);
}
- commit(types.UPDATE_ISSUE_BY_ID, {
- issueId: getters.activeIssue.id,
+ commit(types.UPDATE_BOARD_ITEM_BY_ID, {
+ itemId: getters.activeBoardItem.id,
prop: 'subscribed',
value: data.issueSetSubscription.issue.subscribed,
});
},
setActiveIssueTitle: async ({ commit, getters }, input) => {
- const { activeIssue } = getters;
+ const { activeBoardItem } = getters;
const { data } = await gqlClient.mutate({
mutation: issueSetTitleMutation,
variables: {
input: {
- iid: String(activeIssue.iid),
+ iid: String(activeBoardItem.iid),
projectPath: input.projectPath,
title: input.title,
},
@@ -536,8 +543,8 @@ export default {
throw new Error(data.updateIssue.errors);
}
- commit(types.UPDATE_ISSUE_BY_ID, {
- issueId: activeIssue.id,
+ commit(types.UPDATE_BOARD_ITEM_BY_ID, {
+ itemId: activeBoardItem.id,
prop: 'title',
value: data.updateIssue.issue.title,
});
@@ -578,10 +585,10 @@ export default {
const { selectedBoardItems } = state;
const index = selectedBoardItems.indexOf(boardItem);
- // If user already selected an item (activeIssue) without using mult-select,
+ // If user already selected an item (activeBoardItem) without using mult-select,
// include that item in the selection and unset state.ActiveId to hide the sidebar.
- if (getters.activeIssue) {
- commit(types.ADD_BOARD_ITEM_TO_SELECTION, getters.activeIssue);
+ if (getters.activeBoardItem) {
+ commit(types.ADD_BOARD_ITEM_TO_SELECTION, getters.activeBoardItem);
dispatch('unsetActiveId');
}
diff --git a/app/assets/javascripts/boards/stores/getters.js b/app/assets/javascripts/boards/stores/getters.js
index c819717dade..0589851c658 100644
--- a/app/assets/javascripts/boards/stores/getters.js
+++ b/app/assets/javascripts/boards/stores/getters.js
@@ -15,17 +15,17 @@ export default {
return listItemsIds.map((id) => getters.getBoardItemById(id));
},
- activeIssue: (state) => {
+ activeBoardItem: (state) => {
return state.boardItems[state.activeId] || {};
},
groupPathForActiveIssue: (_, getters) => {
- const { referencePath = '' } = getters.activeIssue;
+ const { referencePath = '' } = getters.activeBoardItem;
return referencePath.slice(0, referencePath.indexOf('/'));
},
projectPathForActiveIssue: (_, getters) => {
- const { referencePath = '' } = getters.activeIssue;
+ const { referencePath = '' } = getters.activeBoardItem;
return referencePath.slice(0, referencePath.indexOf('#'));
},
diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js
index e1f00e4f051..7ff1f8296ca 100644
--- a/app/assets/javascripts/boards/stores/mutation_types.js
+++ b/app/assets/javascripts/boards/stores/mutation_types.js
@@ -20,23 +20,23 @@ export const REMOVE_LIST_FAILURE = 'REMOVE_LIST_FAILURE';
export const REQUEST_ITEMS_FOR_LIST = 'REQUEST_ITEMS_FOR_LIST';
export const RECEIVE_ITEMS_FOR_LIST_FAILURE = 'RECEIVE_ITEMS_FOR_LIST_FAILURE';
export const RECEIVE_ITEMS_FOR_LIST_SUCCESS = 'RECEIVE_ITEMS_FOR_LIST_SUCCESS';
-export const CREATE_ISSUE_FAILURE = 'CREATE_ISSUE_FAILURE';
export const REQUEST_ADD_ISSUE = 'REQUEST_ADD_ISSUE';
export const RECEIVE_ADD_ISSUE_SUCCESS = 'RECEIVE_ADD_ISSUE_SUCCESS';
export const RECEIVE_ADD_ISSUE_ERROR = 'RECEIVE_ADD_ISSUE_ERROR';
export const MOVE_ISSUE = 'MOVE_ISSUE';
export const MOVE_ISSUE_SUCCESS = 'MOVE_ISSUE_SUCCESS';
export const MOVE_ISSUE_FAILURE = 'MOVE_ISSUE_FAILURE';
+export const UPDATE_BOARD_ITEM = 'UPDATE_BOARD_ITEM';
+export const REMOVE_BOARD_ITEM = 'REMOVE_BOARD_ITEM';
export const REQUEST_UPDATE_ISSUE = 'REQUEST_UPDATE_ISSUE';
export const RECEIVE_UPDATE_ISSUE_SUCCESS = 'RECEIVE_UPDATE_ISSUE_SUCCESS';
export const RECEIVE_UPDATE_ISSUE_ERROR = 'RECEIVE_UPDATE_ISSUE_ERROR';
-export const ADD_ISSUE_TO_LIST = 'ADD_ISSUE_TO_LIST';
-export const ADD_ISSUE_TO_LIST_FAILURE = 'ADD_ISSUE_TO_LIST_FAILURE';
-export const REMOVE_ISSUE_FROM_LIST = 'REMOVE_ISSUE_FROM_LIST';
+export const ADD_BOARD_ITEM_TO_LIST = 'ADD_BOARD_ITEM_TO_LIST';
+export const REMOVE_BOARD_ITEM_FROM_LIST = 'REMOVE_BOARD_ITEM_FROM_LIST';
export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
export const SET_ACTIVE_ID = 'SET_ACTIVE_ID';
-export const UPDATE_ISSUE_BY_ID = 'UPDATE_ISSUE_BY_ID';
+export const UPDATE_BOARD_ITEM_BY_ID = 'UPDATE_BOARD_ITEM_BY_ID';
export const SET_ASSIGNEE_LOADING = 'SET_ASSIGNEE_LOADING';
export const RESET_ISSUES = 'RESET_ISSUES';
export const REQUEST_GROUP_PROJECTS = 'REQUEST_GROUP_PROJECTS';
diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js
index 802131d4671..495b3b31df5 100644
--- a/app/assets/javascripts/boards/stores/mutations.js
+++ b/app/assets/javascripts/boards/stores/mutations.js
@@ -158,13 +158,13 @@ export default {
});
},
- [mutationTypes.UPDATE_ISSUE_BY_ID]: (state, { issueId, prop, value }) => {
- if (!state.boardItems[issueId]) {
+ [mutationTypes.UPDATE_BOARD_ITEM_BY_ID]: (state, { itemId, prop, value }) => {
+ if (!state.boardItems[itemId]) {
/* eslint-disable-next-line @gitlab/require-i18n-strings */
throw new Error('No issue found.');
}
- Vue.set(state.boardItems[issueId], prop, value);
+ Vue.set(state.boardItems[itemId], prop, value);
},
[mutationTypes.SET_ASSIGNEE_LOADING](state, isLoading) {
@@ -229,28 +229,23 @@ export default {
notImplemented();
},
- [mutationTypes.CREATE_ISSUE_FAILURE]: (state) => {
- state.error = s__('Boards|An error occurred while creating the issue. Please try again.');
+ [mutationTypes.ADD_BOARD_ITEM_TO_LIST]: (
+ state,
+ { itemId, listId, moveBeforeId, moveAfterId, atIndex },
+ ) => {
+ addItemToList({ state, listId, itemId, moveBeforeId, moveAfterId, atIndex });
},
- [mutationTypes.ADD_ISSUE_TO_LIST]: (state, { list, issue, position }) => {
- addItemToList({
- state,
- listId: list.id,
- itemId: issue.id,
- atIndex: position,
- });
- Vue.set(state.boardItems, issue.id, issue);
+ [mutationTypes.REMOVE_BOARD_ITEM_FROM_LIST]: (state, { itemId, listId }) => {
+ removeItemFromList({ state, listId, itemId });
},
- [mutationTypes.ADD_ISSUE_TO_LIST_FAILURE]: (state, { list, issueId }) => {
- state.error = s__('Boards|An error occurred while creating the issue. Please try again.');
- removeItemFromList({ state, listId: list.id, itemId: issueId });
+ [mutationTypes.UPDATE_BOARD_ITEM]: (state, item) => {
+ Vue.set(state.boardItems, item.id, item);
},
- [mutationTypes.REMOVE_ISSUE_FROM_LIST]: (state, { list, issue }) => {
- removeItemFromList({ state, listId: list.id, itemId: issue.id });
- Vue.delete(state.boardItems, issue.id);
+ [mutationTypes.REMOVE_BOARD_ITEM]: (state, itemId) => {
+ Vue.delete(state.boardItems, itemId);
},
[mutationTypes.SET_CURRENT_PAGE]: () => {
diff --git a/app/assets/stylesheets/page_bundles/boards.scss b/app/assets/stylesheets/page_bundles/boards.scss
index 568d71105fa..e8e3a8f6591 100644
--- a/app/assets/stylesheets/page_bundles/boards.scss
+++ b/app/assets/stylesheets/page_bundles/boards.scss
@@ -365,7 +365,7 @@
margin: 5px;
}
-.right-sidebar.issue-boards-sidebar {
+.right-sidebar.boards-sidebar {
.gutter-toggle {
bottom: 15px;
width: 22px;
@@ -462,7 +462,7 @@
overflow-x: scroll;
}
- .issue-boards-sidebar {
+ .boards-sidebar {
height: 100%;
top: 0;
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 0a36051948e..606a70abbb5 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -287,7 +287,7 @@
padding-top: 10px;
}
- &:not(.issue-boards-sidebar):not([data-signed-in]):not([data-always-show-toggle]) {
+ &:not(.boards-sidebar):not([data-signed-in]):not([data-always-show-toggle]) {
.issuable-sidebar-header {
display: none;
}
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index fbff8ade761..77e5ec209e3 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -286,9 +286,11 @@ module Ci
end
after_transition any => [:failed] do |pipeline|
- next unless pipeline.auto_devops_source?
+ pipeline.run_after_commit do
+ ::Gitlab::Ci::Pipeline::Metrics.pipeline_failure_reason_counter.increment(reason: pipeline.failure_reason)
- pipeline.run_after_commit { AutoDevops::DisableWorker.perform_async(pipeline.id) }
+ AutoDevops::DisableWorker.perform_async(pipeline.id) if pipeline.auto_devops_source?
+ end
end
end
@@ -584,10 +586,18 @@ module Ci
end
def cancel_running(retries: nil)
- retry_optimistic_lock(cancelable_statuses, retries, name: 'ci_pipeline_cancel_running') do |cancelable|
- cancelable.find_each do |job|
- yield(job) if block_given?
- job.cancel
+ commit_status_relations = [:project, :pipeline]
+ ci_build_relations = [:deployment, :taggings]
+
+ retry_optimistic_lock(cancelable_statuses, retries, name: 'ci_pipeline_cancel_running') do |cancelables|
+ cancelables.find_in_batches do |batch|
+ ActiveRecord::Associations::Preloader.new.preload(batch, commit_status_relations)
+ ActiveRecord::Associations::Preloader.new.preload(batch.select { |job| job.is_a?(Ci::Build) }, ci_build_relations)
+
+ batch.each do |job|
+ yield(job) if block_given?
+ job.cancel
+ end
end
end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 292d249cb23..ce253d5ad81 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -181,15 +181,16 @@ class CommitStatus < ApplicationRecord
end
after_transition any => :failed do |commit_status|
- next if Feature.enabled?(:async_add_build_failure_todo, commit_status.project, default_enabled: :yaml)
- next unless commit_status.project
-
- # rubocop: disable CodeReuse/ServiceClass
commit_status.run_after_commit do
- MergeRequests::AddTodoWhenBuildFailsService
- .new(project, nil).execute(self)
+ ::Gitlab::Ci::Pipeline::Metrics.job_failure_reason_counter.increment(reason: commit_status.failure_reason)
+
+ # rubocop: disable CodeReuse/ServiceClass
+ next if Feature.enabled?(:async_add_build_failure_todo, commit_status.project, default_enabled: :yaml)
+ next unless commit_status.project
+
+ MergeRequests::AddTodoWhenBuildFailsService.new(project, nil).execute(self)
+ # rubocop: enable CodeReuse/ServiceClass
end
- # rubocop: enable CodeReuse/ServiceClass
end
end
diff --git a/app/serializers/build_artifact_entity.rb b/app/serializers/build_artifact_entity.rb
index 15458caa9f3..f522e98b448 100644
--- a/app/serializers/build_artifact_entity.rb
+++ b/app/serializers/build_artifact_entity.rb
@@ -15,18 +15,18 @@ class BuildArtifactEntity < Grape::Entity
expose :path do |artifact|
fast_download_project_job_artifacts_path(
- artifact.project,
+ project,
artifact.job,
file_type: artifact.file_type
)
end
- expose :keep_path, if: -> (*) { artifact.expiring? && show_duplicated_paths?(artifact.project) } do |artifact|
- fast_keep_project_job_artifacts_path(artifact.project, artifact.job)
+ expose :keep_path, if: -> (*) { artifact.expiring? && show_duplicated_paths?(project) } do |artifact|
+ fast_keep_project_job_artifacts_path(project, artifact.job)
end
- expose :browse_path, if: -> (*) { show_duplicated_paths?(artifact.project) } do |artifact|
- fast_browse_project_job_artifacts_path(artifact.project, artifact.job)
+ expose :browse_path, if: -> (*) { show_duplicated_paths?(project) } do |artifact|
+ fast_browse_project_job_artifacts_path(project, artifact.job)
end
private
@@ -34,4 +34,8 @@ class BuildArtifactEntity < Grape::Entity
def show_duplicated_paths?(project)
!Gitlab::Ci::Features.remove_duplicate_artifact_exposure_paths?(project)
end
+
+ def project
+ options[:project] || artifact.project
+ end
end
diff --git a/app/serializers/merge_requests/pipeline_entity.rb b/app/serializers/merge_requests/pipeline_entity.rb
index c7caad0e62b..21180ab82ab 100644
--- a/app/serializers/merge_requests/pipeline_entity.rb
+++ b/app/serializers/merge_requests/pipeline_entity.rb
@@ -28,7 +28,7 @@ class MergeRequests::PipelineEntity < Grape::Entity
rel = rel.select { |artifact| can?(request.current_user, :read_job_artifacts, artifact.job) }
end
- BuildArtifactEntity.represent(rel, options)
+ BuildArtifactEntity.represent(rel, options.merge(project: pipeline.project))
end
expose :detailed_status, as: :status, with: DetailedStatusEntity do |pipeline|
diff --git a/app/serializers/pipeline_details_entity.rb b/app/serializers/pipeline_details_entity.rb
index 4fec543eca8..bb6aa2f78ac 100644
--- a/app/serializers/pipeline_details_entity.rb
+++ b/app/serializers/pipeline_details_entity.rb
@@ -15,7 +15,7 @@ class PipelineDetailsEntity < Ci::PipelineEntity
rel = rel.select { |artifact| can?(request.current_user, :read_job_artifacts, artifact.job) }
end
- BuildArtifactEntity.represent(rel, options)
+ BuildArtifactEntity.represent(rel, options.merge(project: pipeline.project))
end
expose :manual_actions, using: BuildActionEntity
expose :scheduled_actions, using: BuildActionEntity
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 970652b4da3..6c69df0c616 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -19,7 +19,7 @@ module Ci
end
def metrics
- @metrics ||= ::Gitlab::Ci::Pipeline::Metrics.new
+ @metrics ||= ::Gitlab::Ci::Pipeline::Metrics
end
private
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index da00879ecf9..106a7832cc7 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -41,25 +41,25 @@
= link_to '#tab-members', class: ['nav-link', ('active' unless invited_active)], data: { toggle: 'tab' } do
%span
= _('Members')
- %span.badge.badge-pill= @members.total_count
+ %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @members.total_count
- if @group.shared_with_group_links.any?
%li.nav-item
= link_to '#tab-groups', class: ['nav-link'] , data: { toggle: 'tab', qa_selector: 'groups_list_tab' } do
%span
= _('Groups')
- %span.badge.badge-pill= @group.shared_with_group_links.count
+ %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @group.shared_with_group_links.count
- if show_invited_members
%li.nav-item
= link_to '#tab-invited-members', class: ['nav-link', ('active' if invited_active)], data: { toggle: 'tab' } do
%span
= _('Invited')
- %span.badge.badge-pill= @invited_members.total_count
+ %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @invited_members.total_count
- if show_access_requests
%li.nav-item
= link_to '#tab-access-requests', class: 'nav-link', data: { toggle: 'tab' } do
%span
= _('Access requests')
- %span.badge.badge-pill= @requesters.count
+ %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @requesters.count
.tab-content
#tab-members.tab-pane{ class: ('active' unless invited_active) }
.js-group-members-list{ data: group_members_list_data_attributes(@group, @members) }
diff --git a/app/views/shared/boards/components/_sidebar.html.haml b/app/views/shared/boards/components/_sidebar.html.haml
index 59dd571604b..8976e89b3d3 100644
--- a/app/views/shared/boards/components/_sidebar.html.haml
+++ b/app/views/shared/boards/components/_sidebar.html.haml
@@ -1,6 +1,6 @@
%board-sidebar{ "inline-template" => true, ":current-user" => (UserSerializer.new.represent(current_user) || {}).to_json }
%transition{ name: "boards-sidebar-slide" }
- %aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar", 'aria-label': s_('Boards|Board') }
+ %aside.right-sidebar.right-sidebar-expanded.boards-sidebar{ "v-show" => "showSidebar", 'aria-label': s_('Boards|Board'), 'data-testid': 'issue-boards-sidebar' }
.issuable-sidebar
.block.issuable-sidebar-header.position-relative
%span.issuable-header-text.hide-collapsed.float-left
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index ef1092b83fa..7466f360f67 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -24,7 +24,7 @@
.avatar-container.s48.flex-grow-0.flex-shrink-0{ class: avatar_container_class }
= link_to project_path(project), class: dom_class(project) do
- if project.creator && use_creator_avatar
- = image_tag avatar_icon_for_user(project.creator, 48), class: "avatar s48", alt:''
+ = image_tag avatar_icon_for_user(project.creator, 48), class: "avatar s48", alt: ''
- else
= project_icon(project, alt: '', class: 'avatar project-avatar s48', width: 48, height: 48)
.project-details.d-sm-flex.flex-sm-fill.align-items-center{ data: { qa_selector: 'project_content', qa_project_name: project.name } }
diff --git a/changelogs/unreleased/21098-reducing-cancel-sql-queries.yml b/changelogs/unreleased/21098-reducing-cancel-sql-queries.yml
new file mode 100644
index 00000000000..61006357e36
--- /dev/null
+++ b/changelogs/unreleased/21098-reducing-cancel-sql-queries.yml
@@ -0,0 +1,5 @@
+---
+title: Preload associations in Ci::Pipeline#cancel_running
+merge_request: 58484
+author:
+type: performance
diff --git a/changelogs/unreleased/board-view-deprecated-button.yml b/changelogs/unreleased/board-view-deprecated-button.yml
new file mode 100644
index 00000000000..cecba250aac
--- /dev/null
+++ b/changelogs/unreleased/board-view-deprecated-button.yml
@@ -0,0 +1,5 @@
+---
+title: Replace deprecated buttons on board view
+merge_request: 58153
+author:
+type: changed
diff --git a/changelogs/unreleased/btn-confirm-project-settings.yml b/changelogs/unreleased/btn-confirm-project-settings.yml
new file mode 100644
index 00000000000..c92b004c918
--- /dev/null
+++ b/changelogs/unreleased/btn-confirm-project-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Move to btn-confirm from btn-success in ee project settings
+merge_request: 58047
+author: Yogi (@yo)
+type: changed
diff --git a/changelogs/unreleased/gl-badge-members.yml b/changelogs/unreleased/gl-badge-members.yml
new file mode 100644
index 00000000000..0a309e7d7ca
--- /dev/null
+++ b/changelogs/unreleased/gl-badge-members.yml
@@ -0,0 +1,5 @@
+---
+title: Add gl-badge for badges in group members page
+merge_request: 57933
+author: Yogi (@yo)
+type: changed
diff --git a/changelogs/unreleased/id-reduce-cached-requests.yml b/changelogs/unreleased/id-reduce-cached-requests.yml
new file mode 100644
index 00000000000..9a08a239c9b
--- /dev/null
+++ b/changelogs/unreleased/id-reduce-cached-requests.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce SQL requests on building artifacts
+merge_request: 58339
+author:
+type: performance
diff --git a/changelogs/unreleased/issue-325836-fix-empty-line-after-let-it-be-groups-module.yml b/changelogs/unreleased/issue-325836-fix-empty-line-after-let-it-be-groups-module.yml
new file mode 100644
index 00000000000..44df3c067dc
--- /dev/null
+++ b/changelogs/unreleased/issue-325836-fix-empty-line-after-let-it-be-groups-module.yml
@@ -0,0 +1,5 @@
+---
+title: Fix EmptyLineAfterFinalLetItBe Rubocop offenses for groups module
+merge_request: 58183
+author: Huzaifa Iftikhar @huzaifaiftikhar
+type: fixed
diff --git a/changelogs/unreleased/issue-325836-fix-empty-line-after-let-it-be-projects-controller.yml b/changelogs/unreleased/issue-325836-fix-empty-line-after-let-it-be-projects-controller.yml
new file mode 100644
index 00000000000..d56d321b93c
--- /dev/null
+++ b/changelogs/unreleased/issue-325836-fix-empty-line-after-let-it-be-projects-controller.yml
@@ -0,0 +1,5 @@
+---
+title: Fix EmptyLineAfterFinalLetItBe Rubocop offenses for projects controller
+merge_request: 58176
+author: Huzaifa Iftikhar @huzaifaiftikhar
+type: other
diff --git a/changelogs/unreleased/mwaw-301055-update-metrics-definitions-for-product-intelligence-group.yml b/changelogs/unreleased/mwaw-301055-update-metrics-definitions-for-product-intelligence-group.yml
new file mode 100644
index 00000000000..1e0527a4150
--- /dev/null
+++ b/changelogs/unreleased/mwaw-301055-update-metrics-definitions-for-product-intelligence-group.yml
@@ -0,0 +1,5 @@
+---
+title: Deprecate Product Intelligence test aggregated metrics
+merge_request: 57377
+author:
+type: deprecated
diff --git a/changelogs/unreleased/use-atomic-mutations-boards.yml b/changelogs/unreleased/use-atomic-mutations-boards.yml
new file mode 100644
index 00000000000..d9916dceaf6
--- /dev/null
+++ b/changelogs/unreleased/use-atomic-mutations-boards.yml
@@ -0,0 +1,5 @@
+---
+title: If creating a new issue fails in boards, remove the issue card from a list
+merge_request: 58558
+author:
+type: other
diff --git a/config/metrics/aggregates/common.yml b/config/metrics/aggregates/common.yml
index 15fc6915977..beabb72dd72 100644
--- a/config/metrics/aggregates/common.yml
+++ b/config/metrics/aggregates/common.yml
@@ -27,22 +27,6 @@
- 'i_compliance_audit_events'
- 'a_compliance_audit_events_api'
- 'i_compliance_credential_inventory'
-- name: product_analytics_test_metrics_union
- operator: OR
- source: redis
- time_frame: [7d, 28d]
- events:
- - 'i_search_total'
- - 'i_search_advanced'
- - 'i_search_paid'
-- name: product_analytics_test_metrics_intersection
- operator: AND
- source: redis
- time_frame: [7d, 28d]
- events:
- - 'i_search_total'
- - 'i_search_advanced'
- - 'i_search_paid'
- name: incident_management_alerts_total_unique_counts
operator: OR
source: redis
diff --git a/config/metrics/counts_28d/20210216183203_product_analytics_test_metrics_union.yml b/config/metrics/counts_28d/20210216183203_product_analytics_test_metrics_union.yml
index f4723c2b5a1..1f9832b220d 100644
--- a/config/metrics/counts_28d/20210216183203_product_analytics_test_metrics_union.yml
+++ b/config/metrics/counts_28d/20210216183203_product_analytics_test_metrics_union.yml
@@ -1,16 +1,21 @@
---
key_path: counts_monthly.aggregated_metrics.product_analytics_test_metrics_union
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: This was test metric used for purpose of assuring correct implementation of aggregated metrics feature
+product_section: growth
+product_stage: growth
+product_group: group::product intelligence
+product_category: collection
value_type: number
-status: data_available
-time_frame: 28d
-data_source: database
+status: removed
+milestone_removed: '13.11'
+milestone: '13.7'
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49886
+time_frame: 7d
+data_source: redis_hll
distribution:
-- ce
+ - ce
+ - ee
tier:
-- free
-skip_validation: true
+ - free
+ - premium
+ - ultimate
diff --git a/config/metrics/counts_28d/20210216183205_product_analytics_test_metrics_intersection.yml b/config/metrics/counts_28d/20210216183205_product_analytics_test_metrics_intersection.yml
index fe8073f3f33..171b2a0a032 100644
--- a/config/metrics/counts_28d/20210216183205_product_analytics_test_metrics_intersection.yml
+++ b/config/metrics/counts_28d/20210216183205_product_analytics_test_metrics_intersection.yml
@@ -1,16 +1,21 @@
---
key_path: counts_monthly.aggregated_metrics.product_analytics_test_metrics_intersection
-description: ''
-product_section: ''
-product_stage: ''
-product_group: ''
-product_category: ''
+description: This was test metric used for purpose of assuring correct implementation of aggregated metrics feature
+product_section: growth
+product_stage: growth
+product_group: group::product intelligence
+product_category: collection
value_type: number
-status: data_available
+status: removed
+milestone_removed: '13.11'
+milestone: '13.7'
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49886
time_frame: 28d
-data_source: database
+data_source: redis_hll
distribution:
-- ce
+ - ce
+ - ee
tier:
-- free
-skip_validation: true
+ - free
+ - premium
+ - ultimate
diff --git a/config/metrics/counts_7d/20210216183213_product_analytics_test_metrics_union.yml b/config/metrics/counts_7d/20210216183213_product_analytics_test_metrics_union.yml
new file mode 100644
index 00000000000..7443c3d599f
--- /dev/null
+++ b/config/metrics/counts_7d/20210216183213_product_analytics_test_metrics_union.yml
@@ -0,0 +1,21 @@
+---
+key_path: counts_weekly.aggregated_metrics.product_analytics_test_metrics_union
+description: This was test metric used for purpose of assuring correct implementation of aggregated metrics feature
+product_section: growth
+product_stage: growth
+product_group: group::product intelligence
+product_category: collection
+value_type: number
+status: removed
+milestone_removed: '13.11'
+milestone: '13.7'
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49886
+time_frame: 7d
+data_source: redis_hll
+distribution:
+ - ce
+ - ee
+tier:
+ - free
+ - premium
+ - ultimate
diff --git a/config/metrics/counts_7d/20210216183215_product_analytics_test_metrics_intersection.yml b/config/metrics/counts_7d/20210216183215_product_analytics_test_metrics_intersection.yml
new file mode 100644
index 00000000000..d4d6a713936
--- /dev/null
+++ b/config/metrics/counts_7d/20210216183215_product_analytics_test_metrics_intersection.yml
@@ -0,0 +1,21 @@
+---
+key_path: counts_weekly.aggregated_metrics.product_analytics_test_metrics_intersection
+description: This was test metric used for purpose of assuring correct implementation of aggregated metrics feature
+product_section: growth
+product_stage: growth
+product_group: group::product intelligence
+product_category: collection
+value_type: number
+status: removed
+milestone_removed: '13.11'
+milestone: '13.7'
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49886
+time_frame: 7d
+data_source: redis_hll
+distribution:
+ - ce
+ - ee
+tier:
+ - free
+ - premium
+ - ultimate
diff --git a/config/metrics/schema.json b/config/metrics/schema.json
index 8878b3bc744..4c10aca7061 100644
--- a/config/metrics/schema.json
+++ b/config/metrics/schema.json
@@ -30,7 +30,7 @@
},
"status": {
"type": ["string"],
- "enum": ["data_available", "implemented", "not_used", "deprecated"]
+ "enum": ["data_available", "implemented", "not_used", "deprecated", "removed"]
},
"milestone": {
"type": ["string", "null"],
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 7fd457cd07d..6b336e60be1 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -601,3 +601,22 @@ gitlab_rails['artifacts_object_store_direct_upload'] = true
To prevent this, comment out or remove those lines, or switch to their [default values](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template),
then run `sudo gitlab-ctl reconfigure`.
+
+### Job artifact upload fails with error 500
+
+If you are using object storage for artifacts and a job artifact fails to upload,
+you can check:
+
+- The job log for an error similar to:
+
+ ```plaintext
+ WARNING: Uploading artifacts as "archive" to coordinator... failed id=12345 responseStatus=500 Internal Server Error status=500 token=abcd1234
+ ```
+
+- The [workhorse log](logs.md#workhorse-logs) for an error similar to:
+
+ ```json
+ {"error":"MissingRegion: could not find region configuration","level":"error","msg":"error uploading S3 session","time":"2021-03-16T22:10:55-04:00"}
+ ```
+
+In both cases, you might need to add `region` to the job artifact [object storage configuration](#connection-settings).
diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md
index 24cd5e10f89..7a0cca9dccb 100644
--- a/doc/development/usage_ping/dictionary.md
+++ b/doc/development/usage_ping/dictionary.md
@@ -6154,27 +6154,27 @@ Tiers: `free`
### `counts_monthly.aggregated_metrics.product_analytics_test_metrics_intersection`
-Missing description
+This was test metric used for purpose of assuring correct implementation of aggregated metrics feature
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183205_product_analytics_test_metrics_intersection.yml)
-Group: ``
+Group: `group::product intelligence`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts_monthly.aggregated_metrics.product_analytics_test_metrics_union`
-Missing description
+This was test metric used for purpose of assuring correct implementation of aggregated metrics feature
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216183203_product_analytics_test_metrics_union.yml)
-Group: ``
+Group: `group::product intelligence`
-Status: `data_available`
+Status: `removed`
-Tiers: `free`
+Tiers: `free`, `premium`, `ultimate`
### `counts_monthly.deployments`
@@ -6322,27 +6322,27 @@ Tiers:
### `counts_weekly.aggregated_metrics.product_analytics_test_metrics_intersection`
-Missing description
+This was test metric used for purpose of assuring correct implementation of aggregated metrics feature
-[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210216183215_product_analytics_test_metrics_intersection.yml)
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210216183215_product_analytics_test_metrics_intersection.yml)
-Group: ``
+Group: `group::product intelligence`
-Status: `data_available`
+Status: `removed`
-Tiers:
+Tiers: `free`, `premium`, `ultimate`
### `counts_weekly.aggregated_metrics.product_analytics_test_metrics_union`
-Missing description
+This was test metric used for purpose of assuring correct implementation of aggregated metrics feature
-[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210216183213_product_analytics_test_metrics_union.yml)
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210216183213_product_analytics_test_metrics_union.yml)
-Group: ``
+Group: `group::product intelligence`
-Status: `data_available`
+Status: `removed`
-Tiers:
+Tiers: `free`, `premium`, `ultimate`
### `database.adapter`
diff --git a/doc/development/usage_ping/metrics_dictionary.md b/doc/development/usage_ping/metrics_dictionary.md
index 047fb04768c..261c5b8078f 100644
--- a/doc/development/usage_ping/metrics_dictionary.md
+++ b/doc/development/usage_ping/metrics_dictionary.md
@@ -33,7 +33,7 @@ Each metric is defined in a separate YAML file consisting of a number of fields:
| `product_group` | yes | The [group](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml) that owns the metric. |
| `product_category` | no | The [product category](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/categories.yml) for the metric. |
| `value_type` | yes | `string`; one of [`string`, `number`, `boolean`, `object`](https://json-schema.org/understanding-json-schema/reference/type.html). |
-| `status` | yes | `string`; [status](#metric-statuses) of the metric, may be set to `data_available`, `implemented`, `not_used`, `deprecated`. |
+| `status` | yes | `string`; [status](#metric-statuses) of the metric, may be set to `data_available`, `implemented`, `not_used`, `deprecated`, `removed`. |
| `time_frame` | yes | `string`; may be set to a value like `7d`, `28d`, `all`, `none`. |
| `data_source` | yes | `string`; may be set to a value like `database`, `redis`, `redis_hll`, `prometheus`, `ruby`. |
| `distribution` | yes | `array`; may be set to one of `ce, ee` or `ee`. The [distribution](https://about.gitlab.com/handbook/marketing/strategic-marketing/tiers/#definitions) where the tracked feature is available. |
@@ -52,6 +52,7 @@ Metric definitions can have one of the following statuses:
status for newly added metrics awaiting inclusion in a new release.
- `not_used`: Metric is not used in any dashboard.
- `deprecated`: Metric is deprecated and possibly planned to be removed.
+- `removed`: Metric was removed, but it may appear in Usage Ping payloads sent from instances running on older versions of GitLab.
### Example YAML metric definition
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index 46ecb10ea2b..c3c1728602c 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -84,7 +84,7 @@ module Gitlab
end
def metrics
- @metrics ||= ::Gitlab::Ci::Pipeline::Metrics.new
+ @metrics ||= ::Gitlab::Ci::Pipeline::Metrics
end
def observe_creation_duration(duration)
@@ -97,6 +97,11 @@ module Gitlab
.observe({ source: pipeline.source.to_s }, pipeline.total_size)
end
+ def increment_pipeline_failure_reason_counter(reason)
+ metrics.pipeline_failure_reason_counter
+ .increment(reason: (reason || :unknown_failure).to_s)
+ end
+
def dangling_build?
%i[ondemand_dast_scan webide].include?(source)
end
diff --git a/lib/gitlab/ci/pipeline/chain/helpers.rb b/lib/gitlab/ci/pipeline/chain/helpers.rb
index d7271df1694..c59a2194823 100644
--- a/lib/gitlab/ci/pipeline/chain/helpers.rb
+++ b/lib/gitlab/ci/pipeline/chain/helpers.rb
@@ -12,7 +12,8 @@ module Gitlab
end
pipeline.add_error_message(message)
- pipeline.drop!(drop_reason) if drop_reason && persist_pipeline?
+
+ drop_pipeline!(drop_reason)
# TODO: consider not to rely on AR errors directly as they can be
# polluted with other unrelated errors (e.g. state machine)
@@ -24,8 +25,16 @@ module Gitlab
pipeline.add_warning_message(message)
end
- def persist_pipeline?
- command.save_incompleted && !pipeline.readonly?
+ private
+
+ def drop_pipeline!(drop_reason)
+ return if pipeline.readonly?
+
+ if drop_reason && command.save_incompleted
+ pipeline.drop!(drop_reason)
+ else
+ command.increment_pipeline_failure_reason_counter(drop_reason)
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/metrics.rb b/lib/gitlab/ci/pipeline/chain/metrics.rb
index 0d7449813b4..b17ae77d445 100644
--- a/lib/gitlab/ci/pipeline/chain/metrics.rb
+++ b/lib/gitlab/ci/pipeline/chain/metrics.rb
@@ -14,7 +14,7 @@ module Gitlab
end
def counter
- ::Gitlab::Ci::Pipeline::Metrics.new.pipelines_created_counter
+ ::Gitlab::Ci::Pipeline::Metrics.pipelines_created_counter
end
end
end
diff --git a/lib/gitlab/ci/pipeline/metrics.rb b/lib/gitlab/ci/pipeline/metrics.rb
index c77f4dcca5a..50cc08c0d66 100644
--- a/lib/gitlab/ci/pipeline/metrics.rb
+++ b/lib/gitlab/ci/pipeline/metrics.rb
@@ -4,9 +4,9 @@ module Gitlab
module Ci
module Pipeline
class Metrics
- include Gitlab::Utils::StrongMemoize
+ extend Gitlab::Utils::StrongMemoize
- def pipeline_creation_duration_histogram
+ def self.pipeline_creation_duration_histogram
strong_memoize(:pipeline_creation_duration_histogram) do
name = :gitlab_ci_pipeline_creation_duration_seconds
comment = 'Pipeline creation duration'
@@ -17,7 +17,7 @@ module Gitlab
end
end
- def pipeline_size_histogram
+ def self.pipeline_size_histogram
strong_memoize(:pipeline_size_histogram) do
name = :gitlab_ci_pipeline_size_builds
comment = 'Pipeline size'
@@ -28,7 +28,7 @@ module Gitlab
end
end
- def pipeline_processing_events_counter
+ def self.pipeline_processing_events_counter
strong_memoize(:pipeline_processing_events_counter) do
name = :gitlab_ci_pipeline_processing_events_total
comment = 'Total amount of pipeline processing events'
@@ -37,7 +37,7 @@ module Gitlab
end
end
- def pipelines_created_counter
+ def self.pipelines_created_counter
strong_memoize(:pipelines_created_count) do
name = :pipelines_created_total
comment = 'Counter of pipelines created'
@@ -46,7 +46,7 @@ module Gitlab
end
end
- def legacy_update_jobs_counter
+ def self.legacy_update_jobs_counter
strong_memoize(:legacy_update_jobs_counter) do
name = :ci_legacy_update_jobs_as_retried_total
comment = 'Counter of occurrences when jobs were not being set as retried before update_retried'
@@ -54,6 +54,24 @@ module Gitlab
Gitlab::Metrics.counter(name, comment)
end
end
+
+ def self.pipeline_failure_reason_counter
+ strong_memoize(:pipeline_failure_reason_counter) do
+ name = :gitlab_ci_pipeline_failure_reasons
+ comment = 'Counter of pipeline failure reasons'
+
+ Gitlab::Metrics.counter(name, comment)
+ end
+ end
+
+ def self.job_failure_reason_counter
+ strong_memoize(:job_failure_reason_counter) do
+ name = :gitlab_ci_job_failure_reasons
+ comment = 'Counter of job failure reasons'
+
+ Gitlab::Metrics.counter(name, comment)
+ end
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0ace5a67816..75054e1b9af 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -12108,6 +12108,9 @@ msgstr ""
msgid "Epic cannot be found."
msgstr ""
+msgid "Epic details"
+msgstr ""
+
msgid "Epic events"
msgstr ""
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb
index 2ddc59acd5c..7c71228c767 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb
@@ -69,14 +69,15 @@ module QA
Page::MergeRequest::Show.perform do |mr|
mr.merge_when_pipeline_succeeds!
- expect(mr.merge_request_status).to match(/to be merged automatically when the pipeline succeeds/)
-
Support::Waiter.wait_until(sleep_interval: 5) do
merge_request = merge_request.reload!
merge_request.state == 'merged'
end
- expect(mr.merged?).to be_truthy, "Expected content 'The changes were merged' but it did not appear."
+ aggregate_failures do
+ expect(merge_request.merge_when_pipeline_succeeds).to be_truthy
+ expect(mr.merged?).to be_truthy, "Expected content 'The changes were merged' but it did not appear."
+ end
end
end
end
diff --git a/spec/controllers/projects/alerting/notifications_controller_spec.rb b/spec/controllers/projects/alerting/notifications_controller_spec.rb
index 3656cfbcc30..fe0c4ce00bf 100644
--- a/spec/controllers/projects/alerting/notifications_controller_spec.rb
+++ b/spec/controllers/projects/alerting/notifications_controller_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::Alerting::NotificationsController do
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
+
let(:params) { project_params }
describe 'POST #create' do
@@ -68,6 +69,7 @@ RSpec.describe Projects::Alerting::NotificationsController do
context 'with a corresponding integration' do
context 'with integration parameters specified' do
let_it_be_with_reload(:integration) { create(:alert_management_http_integration, project: project) }
+
let(:params) { project_params(endpoint_identifier: integration.endpoint_identifier, name: integration.name) }
context 'the integration is active' do
diff --git a/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb b/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb
index e0f86876f67..d2e0def6d0f 100644
--- a/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb
+++ b/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe Projects::DesignManagement::Designs::RawImagesController do
let_it_be(:project) { create(:project, :private) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:viewer) { issue.author }
+
let(:design_id) { design.id }
let(:sha) { design.versions.first.sha }
let(:filename) { design.filename }
diff --git a/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb b/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb
index 96ecbaf55b6..56c0ef592ca 100644
--- a/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb
+++ b/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe Projects::DesignManagement::Designs::ResizedImageController do
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:viewer) { issue.author }
let_it_be(:size) { :v432x230 }
+
let(:design) { create(:design, :with_smaller_image_versions, issue: issue, versions_count: 2) }
let(:design_id) { design.id }
let(:sha) { design.versions.first.sha }
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index 83ad36b217f..4cb90edb742 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe Projects::EnvironmentsController do
let_it_be(:project) { create(:project) }
let_it_be(:maintainer) { create(:user, name: 'main-dos').tap { |u| project.add_maintainer(u) } }
let_it_be(:reporter) { create(:user, name: 'repo-dos').tap { |u| project.add_reporter(u) } }
+
let(:user) { maintainer }
let!(:environment) { create(:environment, name: 'production', project: project) }
diff --git a/spec/controllers/projects/feature_flags_controller_spec.rb b/spec/controllers/projects/feature_flags_controller_spec.rb
index f69cc0ddfd8..cd7d1ea0e8a 100644
--- a/spec/controllers/projects/feature_flags_controller_spec.rb
+++ b/spec/controllers/projects/feature_flags_controller_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe Projects::FeatureFlagsController do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user) }
let_it_be(:reporter) { create(:user) }
+
let(:user) { developer }
before_all do
diff --git a/spec/controllers/projects/incidents_controller_spec.rb b/spec/controllers/projects/incidents_controller_spec.rb
index ddd15b9b1dd..460821634b0 100644
--- a/spec/controllers/projects/incidents_controller_spec.rb
+++ b/spec/controllers/projects/incidents_controller_spec.rb
@@ -69,6 +69,7 @@ RSpec.describe Projects::IncidentsController do
end
let_it_be(:resource) { create(:incident, project: project) }
+
let(:user) { developer }
it 'renders incident page' do
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 992b76ed24a..5be98dbb096 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe Projects::IssuesController do
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:user, reload: true) { create(:user) }
+
let(:issue) { create(:issue, project: project) }
let(:spam_action_response_fields) { { 'stub_spam_action_response_fields' => true } }
@@ -369,6 +370,7 @@ RSpec.describe Projects::IssuesController do
end
let_it_be(:issue) { create(:issue, project: project) }
+
let(:developer) { user }
let(:params) do
{
@@ -1185,6 +1187,7 @@ RSpec.describe Projects::IssuesController do
context 'resolving discussions in MergeRequest' do
let_it_be(:discussion) { create(:diff_note_on_merge_request).to_discussion }
+
let(:merge_request) { discussion.noteable }
let(:project) { merge_request.source_project }
@@ -1648,6 +1651,7 @@ RSpec.describe Projects::IssuesController do
describe 'POST #import_csv' do
let_it_be(:project) { create(:project, :public) }
+
let(:file) { fixture_file_upload('spec/fixtures/csv_comma.csv') }
context 'unauthorized' do
@@ -1847,6 +1851,7 @@ RSpec.describe Projects::IssuesController do
context 'with cross-reference system note', :request_store do
let_it_be(:new_issue) { create(:issue) }
+
let(:cross_reference) { "mentioned in #{new_issue.to_reference(issue.project)}" }
before do
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 80e1268cb01..a7a36d3a074 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -1275,6 +1275,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let_it_be(:reporter) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be(:project) { create(:project, :private, :repository, namespace: owner.namespace) }
+
let(:user) { maintainer }
let(:pipeline) { create(:ci_pipeline, project: project, source: :webide, config_source: :webide_source, user: user) }
let(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline, user: user) }
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index d452f69d6fb..6644373f758 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe Projects::MergeRequestsController do
let_it_be_with_refind(:project) { create(:project, :repository) }
let_it_be_with_reload(:project_public_with_private_builds) { create(:project, :repository, :public, :builds_private) }
+
let(:user) { project.owner }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
diff --git a/spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb b/spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb
index 8a344a72120..923581d9367 100644
--- a/spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb
+++ b/spec/controllers/projects/performance_monitoring/dashboards_controller_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::PerformanceMonitoring::DashboardsController do
let_it_be(:user) { create(:user) }
let_it_be(:namespace) { create(:namespace) }
+
let!(:project) { create(:project, :repository, name: 'dashboard-project', namespace: namespace) }
let(:repository) { project.repository }
let(:branch) { double(name: branch_name) }
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index abc24163e7b..16bce104630 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Projects::PipelinesController do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public, :repository) }
+
let(:feature) { ProjectFeature::ENABLED }
before do
diff --git a/spec/controllers/projects/pipelines_settings_controller_spec.rb b/spec/controllers/projects/pipelines_settings_controller_spec.rb
index ad631b7c3da..39fb153e802 100644
--- a/spec/controllers/projects/pipelines_settings_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_settings_controller_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::PipelinesSettingsController do
let_it_be(:user) { create(:user) }
let_it_be(:project_auto_devops) { create(:project_auto_devops) }
+
let(:project) { project_auto_devops.project }
before do
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index b1c3c1c0276..5dee36ee7c2 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Projects::RawController do
include RepoHelpers
let_it_be(:project) { create(:project, :public, :repository) }
+
let(:inline) { nil }
describe 'GET #show' do
diff --git a/spec/controllers/projects/releases/evidences_controller_spec.rb b/spec/controllers/projects/releases/evidences_controller_spec.rb
index 0a83cdb19fe..68433969d69 100644
--- a/spec/controllers/projects/releases/evidences_controller_spec.rb
+++ b/spec/controllers/projects/releases/evidences_controller_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Projects::Releases::EvidencesController do
let_it_be(:private_project) { create(:project, :repository, :private) }
let_it_be(:developer) { create(:user) }
let_it_be(:reporter) { create(:user) }
+
let(:user) { developer }
before do
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index fc7ab88bbe0..a1e36ec5c4c 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -11,6 +11,7 @@ RSpec.describe Projects::ReleasesController do
let_it_be(:reporter) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be(:user) { developer }
+
let!(:release_1) { create(:release, project: project, released_at: Time.zone.parse('2018-10-18')) }
let!(:release_2) { create(:release, project: project, released_at: Time.zone.parse('2019-10-19')) }
diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
index 7a6e11d53d4..d953249c139 100644
--- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
@@ -5,6 +5,7 @@ require('spec_helper')
RSpec.describe Projects::Settings::CiCdController do
let_it_be(:user) { create(:user) }
let_it_be(:project_auto_devops) { create(:project_auto_devops) }
+
let(:project) { project_auto_devops.project }
before do
diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb
index 46f69eaf96a..d2934ec4e97 100644
--- a/spec/controllers/projects/settings/operations_controller_spec.rb
+++ b/spec/controllers/projects/settings/operations_controller_spec.rb
@@ -493,6 +493,7 @@ RSpec.describe Projects::Settings::OperationsController do
describe 'PATCH #update' do
let_it_be(:external_url) { 'https://gitlab.com' }
+
let(:params) do
{
tracing_setting_attributes: {
diff --git a/spec/controllers/projects/static_site_editor_controller_spec.rb b/spec/controllers/projects/static_site_editor_controller_spec.rb
index b563f3b667f..73b0e3bba69 100644
--- a/spec/controllers/projects/static_site_editor_controller_spec.rb
+++ b/spec/controllers/projects/static_site_editor_controller_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::StaticSiteEditorController do
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:user) { create(:user) }
+
let(:data) { { key: 'value' } }
describe 'GET index' do
diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb
index 0e35f401bc8..9a73417ffdb 100644
--- a/spec/controllers/projects/todos_controller_spec.rb
+++ b/spec/controllers/projects/todos_controller_spec.rb
@@ -5,6 +5,7 @@ require('spec_helper')
RSpec.describe Projects::TodosController do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
+
let(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, source_project: project) }
let(:design) { create(:design, project: project, issue: issue) }
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 1d32f607987..bc903830304 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe ProjectsController do
let_it_be(:project, reload: true) { create(:project, service_desk_enabled: false) }
let_it_be(:public_project) { create(:project, :public) }
let_it_be(:user) { create(:user) }
+
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
@@ -548,6 +549,7 @@ RSpec.describe ProjectsController do
describe '#housekeeping' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
+
let(:housekeeping) { Repositories::HousekeepingService.new(project) }
context 'when authenticated as owner' do
@@ -1097,6 +1099,7 @@ RSpec.describe ProjectsController do
context 'state filter on references' do
let_it_be(:issue) { create(:issue, :closed, project: public_project) }
+
let(:merge_request) { create(:merge_request, :closed, target_project: public_project) }
it 'renders JSON body with state filter for issues' do
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb
index feb0b0b992f..20ae569322c 100644
--- a/spec/features/boards/new_issue_spec.rb
+++ b/spec/features/boards/new_issue_spec.rb
@@ -90,7 +90,7 @@ RSpec.describe 'Issue Boards new issue', :js do
wait_for_requests
- expect(page).to have_selector('.issue-boards-sidebar')
+ expect(page).to have_selector('[data-testid="issue-boards-sidebar"]')
end
it 'successfuly loads labels to be added to newly created issue' do
@@ -109,7 +109,7 @@ RSpec.describe 'Issue Boards new issue', :js do
find('.board-card').click
end
- page.within(first('.issue-boards-sidebar')) do
+ page.within(first('[data-testid="issue-boards-sidebar"]')) do
find('.labels [data-testid="edit-button"]').click
wait_for_requests
diff --git a/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb
index 38deee547a3..d31a7977f66 100644
--- a/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/groups/members/master_adds_member_with_expiration_date_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe 'Groups > Members > Owner adds member with expiration date', :js
let_it_be(:user1) { create(:user, name: 'John Doe') }
let_it_be(:group) { create(:group) }
+
let(:new_member) { create(:user, name: 'Mary Jane') }
before do
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 33afa5d04e5..22785adae4a 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -244,6 +244,7 @@ RSpec.describe 'Group' do
describe 'group edit', :js do
let_it_be(:group) { create(:group, :public) }
+
let(:path) { edit_group_path(group) }
let(:new_name) { 'new-name' }
@@ -289,6 +290,7 @@ RSpec.describe 'Group' do
describe 'group page with markdown description' do
let_it_be(:group) { create(:group) }
+
let(:path) { group_path(group) }
before do
diff --git a/spec/frontend/boards/components/board_content_sidebar_spec.js b/spec/frontend/boards/components/board_content_sidebar_spec.js
index ec61cab08aa..7c18f0a438e 100644
--- a/spec/frontend/boards/components/board_content_sidebar_spec.js
+++ b/spec/frontend/boards/components/board_content_sidebar_spec.js
@@ -24,7 +24,7 @@ describe('BoardContentSidebar', () => {
issuableType: 'issue',
},
getters: {
- activeIssue: () => {
+ activeBoardItem: () => {
return { ...mockIssue, epic: null };
},
groupPathForActiveIssue: () => mockIssueGroupPath,
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index 32499bd5480..24fcdd528d5 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -226,7 +226,7 @@ describe('BoardForm', () => {
it('passes correct primary action text and variant', () => {
expect(findModalActionPrimary().text).toBe('Save changes');
- expect(findModalActionPrimary().attributes[0].variant).toBe('info');
+ expect(findModalActionPrimary().attributes[0].variant).toBe('confirm');
});
it('does not render delete confirmation message', () => {
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
index 98ac211238c..153d0640b23 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
@@ -64,7 +64,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
beforeEach(async () => {
createWrapper();
- jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => TEST_LABELS);
+ jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => TEST_LABELS);
findLabelsSelect().vm.$emit('updateSelectedLabels', TEST_LABELS_PAYLOAD);
store.state.boardItems[TEST_ISSUE.id].labels = TEST_LABELS;
await wrapper.vm.$nextTick();
@@ -76,7 +76,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
});
it('commits change to the server', () => {
- expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({
+ expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({
addLabelIds: TEST_LABELS.map((label) => label.id),
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
removeLabelIds: [],
@@ -94,13 +94,13 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
beforeEach(async () => {
createWrapper({ labels: TEST_LABELS });
- jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => expectedLabels);
+ jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => expectedLabels);
findLabelsSelect().vm.$emit('updateSelectedLabels', testLabelsPayload);
await wrapper.vm.$nextTick();
});
it('commits change to the server', () => {
- expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({
+ expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({
addLabelIds: [5, 7],
removeLabelIds: [6],
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
@@ -114,13 +114,13 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
beforeEach(async () => {
createWrapper({ labels: [testLabel] });
- jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => {});
+ jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => {});
});
it('commits change to the server', () => {
wrapper.find(GlLabel).vm.$emit('close', testLabel);
- expect(wrapper.vm.setActiveIssueLabels).toHaveBeenCalledWith({
+ expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({
removeLabelIds: [getIdFromGraphQLId(testLabel.id)],
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
});
@@ -131,7 +131,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
beforeEach(async () => {
createWrapper({ labels: TEST_LABELS });
- jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => {
+ jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => {
throw new Error(['failed mutation']);
});
findLabelsSelect().vm.$emit('updateSelectedLabels', [{ id: '?' }]);
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
index cfd7f32b2cc..1d9b6eb31ab 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
@@ -20,10 +20,10 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
const findToggle = () => wrapper.find(GlToggle);
const findGlLoadingIcon = () => wrapper.find(GlLoadingIcon);
- const createComponent = (activeIssue = { ...mockActiveIssue }) => {
+ const createComponent = (activeBoardItem = { ...mockActiveIssue }) => {
store = createStore();
- store.state.boardItems = { [activeIssue.id]: activeIssue };
- store.state.activeId = activeIssue.id;
+ store.state.boardItems = { [activeBoardItem.id]: activeBoardItem };
+ store.state.activeId = activeBoardItem.id;
wrapper = mount(BoardSidebarSubscription, {
localVue,
@@ -91,8 +91,8 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
describe('Board sidebar subscription component `behavior`', () => {
const mockSetActiveIssueSubscribed = (subscribedState) => {
jest.spyOn(wrapper.vm, 'setActiveIssueSubscribed').mockImplementation(async () => {
- store.commit(types.UPDATE_ISSUE_BY_ID, {
- issueId: mockActiveIssue.id,
+ store.commit(types.UPDATE_BOARD_ITEM_BY_ID, {
+ itemId: mockActiveIssue.id,
prop: 'subscribed',
value: subscribedState,
});
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index 7511a4ad2cb..8cb2c35d503 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -5,6 +5,7 @@ import {
formatListIssues,
formatBoardLists,
formatIssueInput,
+ formatIssue,
} from '~/boards/boards_util';
import { inactiveId, ISSUABLE } from '~/boards/constants';
import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql';
@@ -12,6 +13,7 @@ import issueCreateMutation from '~/boards/graphql/issue_create.mutation.graphql'
import issueMoveListMutation from '~/boards/graphql/issue_move_list.mutation.graphql';
import actions, { gqlClient } from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import {
mockLists,
mockListsById,
@@ -802,11 +804,11 @@ describe('setAssignees', () => {
testAction(
actions.setAssignees,
[node],
- { activeIssue: { iid, referencePath: refPath }, commit: () => {} },
+ { activeBoardItem: { iid, referencePath: refPath }, commit: () => {} },
[
{
- type: 'UPDATE_ISSUE_BY_ID',
- payload: { prop: 'assignees', issueId: undefined, value: [node] },
+ type: 'UPDATE_BOARD_ITEM_BY_ID',
+ payload: { prop: 'assignees', itemId: undefined, value: [node] },
},
],
[],
@@ -816,7 +818,43 @@ describe('setAssignees', () => {
});
});
-describe('createNewIssue', () => {
+describe('addListItem', () => {
+ it('should commit ADD_BOARD_ITEM_TO_LIST and UPDATE_BOARD_ITEM mutations', () => {
+ const payload = {
+ list: mockLists[0],
+ item: mockIssue,
+ position: 0,
+ };
+
+ testAction(actions.addListItem, payload, {}, [
+ {
+ type: types.ADD_BOARD_ITEM_TO_LIST,
+ payload: {
+ listId: mockLists[0].id,
+ itemId: mockIssue.id,
+ atIndex: 0,
+ },
+ },
+ { type: types.UPDATE_BOARD_ITEM, payload: mockIssue },
+ ]);
+ });
+});
+
+describe('removeListItem', () => {
+ it('should commit REMOVE_BOARD_ITEM_FROM_LIST and REMOVE_BOARD_ITEM mutations', () => {
+ const payload = {
+ listId: mockLists[0].id,
+ itemId: mockIssue.id,
+ };
+
+ testAction(actions.removeListItem, payload, {}, [
+ { type: types.REMOVE_BOARD_ITEM_FROM_LIST, payload },
+ { type: types.REMOVE_BOARD_ITEM, payload: mockIssue.id },
+ ]);
+ });
+});
+
+describe('addListNewIssue', () => {
const state = {
boardType: 'group',
fullPath: 'gitlab-org/gitlab',
@@ -843,19 +881,7 @@ describe('createNewIssue', () => {
},
};
- it('should return issue from API on success', async () => {
- jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
- data: {
- createIssue: {
- issue: mockIssue,
- errors: [],
- },
- },
- });
-
- const result = await actions.createNewIssue({ state }, mockIssue);
- expect(result).toEqual(mockIssue);
- });
+ const fakeList = { id: 'gid://gitlab/List/123' };
it('should add board scope to the issue being created', async () => {
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
@@ -867,7 +893,11 @@ describe('createNewIssue', () => {
},
});
- await actions.createNewIssue({ state: stateWithBoardConfig }, mockIssue);
+ await actions.addListNewIssue(
+ { dispatch: jest.fn(), commit: jest.fn(), state: stateWithBoardConfig },
+ { issueInput: mockIssue, list: fakeList },
+ );
+
expect(gqlClient.mutate).toHaveBeenCalledWith({
mutation: issueCreateMutation,
variables: {
@@ -894,7 +924,11 @@ describe('createNewIssue', () => {
const payload = formatIssueInput(issue, stateWithBoardConfig.boardConfig);
- await actions.createNewIssue({ state: stateWithBoardConfig }, issue);
+ await actions.addListNewIssue(
+ { dispatch: jest.fn(), commit: jest.fn(), state: stateWithBoardConfig },
+ { issueInput: issue, list: fakeList },
+ );
+
expect(gqlClient.mutate).toHaveBeenCalledWith({
mutation: issueCreateMutation,
variables: {
@@ -905,51 +939,92 @@ describe('createNewIssue', () => {
expect(payload.assigneeIds).toEqual(['gid://gitlab/User/1', 'gid://gitlab/User/2']);
});
- it('should commit CREATE_ISSUE_FAILURE mutation when API returns an error', (done) => {
- jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
- data: {
- createIssue: {
- issue: mockIssue,
- errors: [{ foo: 'bar' }],
+ describe('when issue creation mutation request succeeds', () => {
+ it('dispatches a correct set of mutations', () => {
+ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
+ data: {
+ createIssue: {
+ issue: mockIssue,
+ errors: [],
+ },
},
- },
+ });
+
+ testAction({
+ action: actions.addListNewIssue,
+ payload: {
+ issueInput: mockIssue,
+ list: fakeList,
+ placeholderId: 'tmp',
+ },
+ state,
+ expectedActions: [
+ {
+ type: 'addListItem',
+ payload: {
+ list: fakeList,
+ item: formatIssue({ ...mockIssue, id: 'tmp' }),
+ position: 0,
+ },
+ },
+ { type: 'removeListItem', payload: { listId: fakeList.id, itemId: 'tmp' } },
+ {
+ type: 'addListItem',
+ payload: {
+ list: fakeList,
+ item: formatIssue({ ...mockIssue, id: getIdFromGraphQLId(mockIssue.id) }),
+ position: 0,
+ },
+ },
+ ],
+ });
});
-
- const payload = mockIssue;
-
- testAction(
- actions.createNewIssue,
- payload,
- state,
- [{ type: types.CREATE_ISSUE_FAILURE }],
- [],
- done,
- );
});
-});
-describe('addListIssue', () => {
- it('should commit ADD_ISSUE_TO_LIST mutation', (done) => {
- const payload = {
- list: mockLists[0],
- issue: mockIssue,
- position: 0,
- };
-
- testAction(
- actions.addListIssue,
- payload,
- {},
- [{ type: types.ADD_ISSUE_TO_LIST, payload }],
- [],
- done,
- );
+ describe('when issue creation mutation request fails', () => {
+ it('dispatches a correct set of mutations', () => {
+ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
+ data: {
+ createIssue: {
+ issue: mockIssue,
+ errors: [{ foo: 'bar' }],
+ },
+ },
+ });
+
+ testAction({
+ action: actions.addListNewIssue,
+ payload: {
+ issueInput: mockIssue,
+ list: fakeList,
+ placeholderId: 'tmp',
+ },
+ state,
+ expectedActions: [
+ {
+ type: 'addListItem',
+ payload: {
+ list: fakeList,
+ item: formatIssue({ ...mockIssue, id: 'tmp' }),
+ position: 0,
+ },
+ },
+ { type: 'removeListItem', payload: { listId: fakeList.id, itemId: 'tmp' } },
+ ],
+ expectedMutations: [
+ {
+ type: types.SET_ERROR,
+ payload: 'An error occurred while creating the issue. Please try again.',
+ },
+ ],
+ });
+ });
});
});
describe('setActiveIssueLabels', () => {
const state = { boardItems: { [mockIssue.id]: mockIssue } };
- const getters = { activeIssue: mockIssue };
+ const getters = { activeBoardItem: mockIssue };
const testLabelIds = labels.map((label) => label.id);
const input = {
addLabelIds: testLabelIds,
@@ -963,7 +1038,7 @@ describe('setActiveIssueLabels', () => {
.mockResolvedValue({ data: { updateIssue: { issue: { labels: { nodes: labels } } } } });
const payload = {
- issueId: getters.activeIssue.id,
+ itemId: getters.activeBoardItem.id,
prop: 'labels',
value: labels,
};
@@ -974,7 +1049,7 @@ describe('setActiveIssueLabels', () => {
{ ...state, ...getters },
[
{
- type: types.UPDATE_ISSUE_BY_ID,
+ type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
@@ -994,7 +1069,7 @@ describe('setActiveIssueLabels', () => {
describe('setActiveIssueDueDate', () => {
const state = { boardItems: { [mockIssue.id]: mockIssue } };
- const getters = { activeIssue: mockIssue };
+ const getters = { activeBoardItem: mockIssue };
const testDueDate = '2020-02-20';
const input = {
dueDate: testDueDate,
@@ -1014,7 +1089,7 @@ describe('setActiveIssueDueDate', () => {
});
const payload = {
- issueId: getters.activeIssue.id,
+ itemId: getters.activeBoardItem.id,
prop: 'dueDate',
value: testDueDate,
};
@@ -1025,7 +1100,7 @@ describe('setActiveIssueDueDate', () => {
{ ...state, ...getters },
[
{
- type: types.UPDATE_ISSUE_BY_ID,
+ type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
@@ -1045,7 +1120,7 @@ describe('setActiveIssueDueDate', () => {
describe('setActiveIssueSubscribed', () => {
const state = { boardItems: { [mockActiveIssue.id]: mockActiveIssue } };
- const getters = { activeIssue: mockActiveIssue };
+ const getters = { activeBoardItem: mockActiveIssue };
const subscribedState = true;
const input = {
subscribedState,
@@ -1065,7 +1140,7 @@ describe('setActiveIssueSubscribed', () => {
});
const payload = {
- issueId: getters.activeIssue.id,
+ itemId: getters.activeBoardItem.id,
prop: 'subscribed',
value: subscribedState,
};
@@ -1076,7 +1151,7 @@ describe('setActiveIssueSubscribed', () => {
{ ...state, ...getters },
[
{
- type: types.UPDATE_ISSUE_BY_ID,
+ type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
@@ -1096,7 +1171,7 @@ describe('setActiveIssueSubscribed', () => {
describe('setActiveIssueMilestone', () => {
const state = { boardItems: { [mockIssue.id]: mockIssue } };
- const getters = { activeIssue: mockIssue };
+ const getters = { activeBoardItem: mockIssue };
const testMilestone = {
...mockMilestone,
id: 'gid://gitlab/Milestone/1',
@@ -1119,7 +1194,7 @@ describe('setActiveIssueMilestone', () => {
});
const payload = {
- issueId: getters.activeIssue.id,
+ itemId: getters.activeBoardItem.id,
prop: 'milestone',
value: testMilestone,
};
@@ -1130,7 +1205,7 @@ describe('setActiveIssueMilestone', () => {
{ ...state, ...getters },
[
{
- type: types.UPDATE_ISSUE_BY_ID,
+ type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
@@ -1150,7 +1225,7 @@ describe('setActiveIssueMilestone', () => {
describe('setActiveIssueTitle', () => {
const state = { boardItems: { [mockIssue.id]: mockIssue } };
- const getters = { activeIssue: mockIssue };
+ const getters = { activeBoardItem: mockIssue };
const testTitle = 'Test Title';
const input = {
title: testTitle,
@@ -1170,7 +1245,7 @@ describe('setActiveIssueTitle', () => {
});
const payload = {
- issueId: getters.activeIssue.id,
+ itemId: getters.activeBoardItem.id,
prop: 'title',
value: testTitle,
};
@@ -1181,7 +1256,7 @@ describe('setActiveIssueTitle', () => {
{ ...state, ...getters },
[
{
- type: types.UPDATE_ISSUE_BY_ID,
+ type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
@@ -1325,7 +1400,7 @@ describe('toggleBoardItemMultiSelection', () => {
testAction(
actions.toggleBoardItemMultiSelection,
boardItem2,
- { activeId: mockActiveIssue.id, activeIssue: mockActiveIssue, selectedBoardItems: [] },
+ { activeId: mockActiveIssue.id, activeBoardItem: mockActiveIssue, selectedBoardItems: [] },
[
{
type: types.ADD_BOARD_ITEM_TO_SELECTION,
diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js
index 0541c40061c..6114ba0af5f 100644
--- a/spec/frontend/boards/stores/getters_spec.js
+++ b/spec/frontend/boards/stores/getters_spec.js
@@ -88,7 +88,7 @@ describe('Boards - Getters', () => {
});
});
- describe('activeIssue', () => {
+ describe('activeBoardItem', () => {
it.each`
id | expected
${'1'} | ${'issue'}
@@ -96,7 +96,7 @@ describe('Boards - Getters', () => {
`('returns $expected when $id is passed to state', ({ id, expected }) => {
const state = { boardItems: { 1: 'issue' }, activeId: id };
- expect(getters.activeIssue(state)).toEqual(expected);
+ expect(getters.activeBoardItem(state)).toEqual(expected);
});
});
@@ -105,14 +105,14 @@ describe('Boards - Getters', () => {
const mockActiveIssue = {
referencePath: 'gitlab-org/gitlab-test#1',
};
- expect(getters.groupPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual(
+ expect(getters.groupPathForActiveIssue({}, { activeBoardItem: mockActiveIssue })).toEqual(
'gitlab-org',
);
});
it('returns empty string as group path when active issue is an empty object', () => {
const mockActiveIssue = {};
- expect(getters.groupPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual('');
+ expect(getters.groupPathForActiveIssue({}, { activeBoardItem: mockActiveIssue })).toEqual('');
});
});
@@ -121,14 +121,16 @@ describe('Boards - Getters', () => {
const mockActiveIssue = {
referencePath: 'gitlab-org/gitlab-test#1',
};
- expect(getters.projectPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual(
+ expect(getters.projectPathForActiveIssue({}, { activeBoardItem: mockActiveIssue })).toEqual(
'gitlab-org/gitlab-test',
);
});
it('returns empty string as project path when active issue is an empty object', () => {
const mockActiveIssue = {};
- expect(getters.projectPathForActiveIssue({}, { activeIssue: mockActiveIssue })).toEqual('');
+ expect(getters.projectPathForActiveIssue({}, { activeBoardItem: mockActiveIssue })).toEqual(
+ '',
+ );
});
});
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index d781f403b93..ded69fc0e0f 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -1,3 +1,4 @@
+import { cloneDeep } from 'lodash';
import { issuableTypes } from '~/boards/constants';
import * as types from '~/boards/stores/mutation_types';
import mutations from '~/boards/stores/mutations';
@@ -9,6 +10,7 @@ import {
mockIssue2,
mockGroupProjects,
labels,
+ mockList,
} from '../mock_data';
const expectNotImplemented = (action) => {
@@ -25,6 +27,14 @@ describe('Board Store Mutations', () => {
'gid://gitlab/List/2': mockLists[1],
};
+ const setBoardsListsState = () => {
+ state = cloneDeep({
+ ...state,
+ boardItemsByListId: { 'gid://gitlab/List/1': [mockIssue.id] },
+ boardLists: { 'gid://gitlab/List/1': mockList },
+ });
+ };
+
beforeEach(() => {
state = defaultState();
});
@@ -335,7 +345,7 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.REQUEST_ADD_ISSUE);
});
- describe('UPDATE_ISSUE_BY_ID', () => {
+ describe('UPDATE_BOARD_ITEM_BY_ID', () => {
const issueId = '1';
const prop = 'id';
const value = '2';
@@ -353,8 +363,8 @@ describe('Board Store Mutations', () => {
describe('when the issue is in state', () => {
it('updates the property of the correct issue', () => {
- mutations.UPDATE_ISSUE_BY_ID(state, {
- issueId,
+ mutations.UPDATE_BOARD_ITEM_BY_ID(state, {
+ itemId: issueId,
prop,
value,
});
@@ -366,8 +376,8 @@ describe('Board Store Mutations', () => {
describe('when the issue is not in state', () => {
it('throws an error', () => {
expect(() => {
- mutations.UPDATE_ISSUE_BY_ID(state, {
- issueId: '3',
+ mutations.UPDATE_BOARD_ITEM_BY_ID(state, {
+ itemId: '3',
prop,
value,
});
@@ -467,6 +477,27 @@ describe('Board Store Mutations', () => {
});
});
+ describe('UPDATE_BOARD_ITEM', () => {
+ it('updates the given issue in state.boardItems', () => {
+ const updatedIssue = { id: 'some_gid', foo: 'bar' };
+ state = { boardItems: { some_gid: { id: 'some_gid' } } };
+
+ mutations.UPDATE_BOARD_ITEM(state, updatedIssue);
+
+ expect(state.boardItems.some_gid).toEqual(updatedIssue);
+ });
+ });
+
+ describe('REMOVE_BOARD_ITEM', () => {
+ it('removes the given issue from state.boardItems', () => {
+ state = { boardItems: { some_gid: {}, some_gid2: {} } };
+
+ mutations.REMOVE_BOARD_ITEM(state, 'some_gid');
+
+ expect(state.boardItems).toEqual({ some_gid2: {} });
+ });
+ });
+
describe('REQUEST_UPDATE_ISSUE', () => {
expectNotImplemented(mutations.REQUEST_UPDATE_ISSUE);
});
@@ -479,85 +510,89 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.RECEIVE_UPDATE_ISSUE_ERROR);
});
- describe('CREATE_ISSUE_FAILURE', () => {
- it('sets error message on state', () => {
- mutations.CREATE_ISSUE_FAILURE(state);
+ describe('ADD_BOARD_ITEM_TO_LIST', () => {
+ beforeEach(() => {
+ setBoardsListsState();
+ });
+
+ it.each([
+ [
+ 'at position 0 by default',
+ {
+ payload: {
+ itemId: mockIssue2.id,
+ listId: mockList.id,
+ },
+ listState: [mockIssue2.id, mockIssue.id],
+ },
+ ],
+ [
+ 'at a given position',
+ {
+ payload: {
+ itemId: mockIssue2.id,
+ listId: mockList.id,
+ atIndex: 1,
+ },
+ listState: [mockIssue.id, mockIssue2.id],
+ },
+ ],
+ [
+ "below the issue with id of 'moveBeforeId'",
+ {
+ payload: {
+ itemId: mockIssue2.id,
+ listId: mockList.id,
+ moveBeforeId: mockIssue.id,
+ },
+ listState: [mockIssue.id, mockIssue2.id],
+ },
+ ],
+ [
+ "above the issue with id of 'moveAfterId'",
+ {
+ payload: {
+ itemId: mockIssue2.id,
+ listId: mockList.id,
+ moveAfterId: mockIssue.id,
+ },
+ listState: [mockIssue2.id, mockIssue.id],
+ },
+ ],
+ ])(`inserts an item into a list %s`, (_, { payload, listState }) => {
+ mutations.ADD_BOARD_ITEM_TO_LIST(state, payload);
- expect(state.error).toBe('An error occurred while creating the issue. Please try again.');
+ expect(state.boardItemsByListId[payload.listId]).toEqual(listState);
});
- });
-
- describe('ADD_ISSUE_TO_LIST', () => {
- it('adds issue to issues state and issue id in list in boardItemsByListId', () => {
- const listIssues = {
- 'gid://gitlab/List/1': [mockIssue.id],
- };
- const issues = {
- 1: mockIssue,
- };
-
- state = {
- ...state,
- boardItemsByListId: listIssues,
- boardItems: issues,
- boardLists: initialBoardListsState,
- };
+ it("updates the list's items count", () => {
expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(1);
- mutations.ADD_ISSUE_TO_LIST(state, { list: mockLists[0], issue: mockIssue2 });
+ mutations.ADD_BOARD_ITEM_TO_LIST(state, {
+ itemId: mockIssue2.id,
+ listId: mockList.id,
+ });
- expect(state.boardItemsByListId['gid://gitlab/List/1']).toContain(mockIssue2.id);
- expect(state.boardItems[mockIssue2.id]).toEqual(mockIssue2);
expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(2);
});
});
- describe('ADD_ISSUE_TO_LIST_FAILURE', () => {
- it('removes issue id from list in boardItemsByListId and sets error message', () => {
- const listIssues = {
- 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
- };
- const issues = {
- 1: mockIssue,
- 2: mockIssue2,
- };
-
- state = {
- ...state,
- boardItemsByListId: listIssues,
- boardItems: issues,
- boardLists: initialBoardListsState,
- };
-
- mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issueId: mockIssue2.id });
-
- expect(state.boardItemsByListId['gid://gitlab/List/1']).not.toContain(mockIssue2.id);
- expect(state.error).toBe('An error occurred while creating the issue. Please try again.');
+ describe('REMOVE_BOARD_ITEM_FROM_LIST', () => {
+ beforeEach(() => {
+ setBoardsListsState();
});
- });
-
- describe('REMOVE_ISSUE_FROM_LIST', () => {
- it('removes issue id from list in boardItemsByListId and deletes issue from state', () => {
- const listIssues = {
- 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
- };
- const issues = {
- 1: mockIssue,
- 2: mockIssue2,
- };
- state = {
- ...state,
- boardItemsByListId: listIssues,
- boardItems: issues,
- boardLists: initialBoardListsState,
- };
+ it("removes an item from a list and updates the list's items count", () => {
+ expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(1);
+ expect(state.boardItemsByListId['gid://gitlab/List/1']).toContain(mockIssue.id);
- mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issueId: mockIssue2.id });
+ mutations.REMOVE_BOARD_ITEM_FROM_LIST(state, {
+ itemId: mockIssue.id,
+ listId: mockList.id,
+ });
- expect(state.boardItemsByListId['gid://gitlab/List/1']).not.toContain(mockIssue2.id);
- expect(state.boardItems).not.toContain(mockIssue2);
+ expect(state.boardItemsByListId['gid://gitlab/List/1']).not.toContain(mockIssue.id);
+ expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(0);
});
});
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index 9ca5aeeea58..900dfec38e2 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -321,4 +321,25 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
it { is_expected.to be_falsey }
end
end
+
+ describe '#increment_pipeline_failure_reason_counter' do
+ let(:command) { described_class.new }
+ let(:reason) { :size_limit_exceeded }
+
+ subject { command.increment_pipeline_failure_reason_counter(reason) }
+
+ it 'increments the error metric' do
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { subject }.to change { counter.get(reason: reason.to_s) }.by(1)
+ end
+
+ context 'when the reason is nil' do
+ let(:reason) { nil }
+
+ it 'increments the error metric with unknown_failure' do
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { subject }.to change { counter.get(reason: 'unknown_failure') }.by(1)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
index 78363be7f36..23cdec61bb3 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
let(:save_incompleted) { false }
let(:command) do
- double(:command,
+ Gitlab::Ci::Pipeline::Chain::Command.new(
project: project,
pipeline_seed: pipeline_seed,
save_incompleted: save_incompleted
@@ -49,6 +49,11 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
expect(pipeline.deployments_limit_exceeded?).to be true
end
+
+ it 'calls increment_pipeline_failure_reason_counter' do
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { perform }.to change { counter.get(reason: 'deployments_limit_exceeded') }.by(1)
+ end
end
context 'when not saving incomplete pipelines' do
@@ -71,6 +76,12 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
expect(pipeline.errors.messages).to include(base: ['Pipeline has too many deployments! Requested 2, but the limit is 1.'])
end
+
+ it 'increments the error metric' do
+ expect(command).to receive(:increment_pipeline_failure_reason_counter).with(:deployments_limit_exceeded)
+
+ perform
+ end
end
it 'logs the error' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 53d8ca740b5..62de4d2e96d 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -96,6 +96,11 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Populate do
it 'wastes pipeline iid' do
expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0
end
+
+ it 'increments the error metric' do
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { run_chain }.to change { counter.get(reason: 'unknown_failure') }.by(1)
+ end
end
describe 'pipeline protect' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 161e02ad58d..6d96ef2f245 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -2635,6 +2635,37 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
expect(latest_status).to eq %w(canceled canceled)
end
end
+
+ context 'preloading relations' do
+ let(:pipeline1) { create(:ci_empty_pipeline, :created) }
+ let(:pipeline2) { create(:ci_empty_pipeline, :created) }
+
+ before do
+ create(:ci_build, :pending, pipeline: pipeline1)
+ create(:generic_commit_status, :pending, pipeline: pipeline1)
+
+ create(:ci_build, :pending, pipeline: pipeline2)
+ create(:ci_build, :pending, pipeline: pipeline2)
+ create(:generic_commit_status, :pending, pipeline: pipeline2)
+ create(:generic_commit_status, :pending, pipeline: pipeline2)
+ create(:generic_commit_status, :pending, pipeline: pipeline2)
+ end
+
+ it 'preloads relations for each build to avoid N+1 queries' do
+ control1 = ActiveRecord::QueryRecorder.new do
+ pipeline1.cancel_running
+ end
+
+ control2 = ActiveRecord::QueryRecorder.new do
+ pipeline2.cancel_running
+ end
+
+ extra_update_queries = 3 # transition ... => :canceled
+ extra_generic_commit_status_validation_queries = 2 # name_uniqueness_across_types
+
+ expect(control2.count).to eq(control1.count + extra_update_queries + extra_generic_commit_status_validation_queries)
+ end
+ end
end
describe '#retry_failed' do
@@ -3836,6 +3867,16 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
pipeline.drop
end
end
+
+ context 'with failure_reason' do
+ let(:pipeline) { create(:ci_pipeline, :running) }
+ let(:failure_reason) { 'config_error' }
+ let(:counter) { Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc') }
+
+ it 'increments the counter with the failure_reason' do
+ expect { pipeline.drop!(failure_reason) }.to change { counter.get(reason: failure_reason) }.by(1)
+ end
+ end
end
end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 35aa9b05b70..3bdeaff475a 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -629,30 +629,45 @@ RSpec.describe CommitStatus do
end
end
- describe 'set failure_reason when drop' do
+ describe '#drop' do
let(:commit_status) { create(:commit_status, :created) }
+ let(:counter) { Gitlab::Metrics.counter(:gitlab_ci_job_failure_reasons, 'desc') }
+ let(:failure_reason) { reason.to_s }
subject do
commit_status.drop!(reason)
commit_status
end
+ shared_examples 'incrementing failure reason counter' do
+ it 'increments the counter with the failure_reason' do
+ expect { subject }.to change { counter.get(reason: failure_reason) }.by(1)
+ end
+ end
+
context 'when failure_reason is nil' do
let(:reason) { }
+ let(:failure_reason) { 'unknown_failure' }
it { is_expected.to be_unknown_failure }
+
+ it_behaves_like 'incrementing failure reason counter'
end
context 'when failure_reason is script_failure' do
let(:reason) { :script_failure }
it { is_expected.to be_script_failure }
+
+ it_behaves_like 'incrementing failure reason counter'
end
context 'when failure_reason is unmet_prerequisites' do
let(:reason) { :unmet_prerequisites }
it { is_expected.to be_unmet_prerequisites }
+
+ it_behaves_like 'incrementing failure reason counter'
end
end
diff --git a/spec/serializers/build_artifact_entity_spec.rb b/spec/serializers/build_artifact_entity_spec.rb
index 87c1874ec41..3d4dc3f69c9 100644
--- a/spec/serializers/build_artifact_entity_spec.rb
+++ b/spec/serializers/build_artifact_entity_spec.rb
@@ -3,11 +3,13 @@
require 'spec_helper'
RSpec.describe BuildArtifactEntity do
- let(:job) { create(:ci_build) }
- let(:artifact) { create(:ci_job_artifact, :codequality, expire_at: 1.hour.from_now, job: job) }
+ let_it_be(:job) { create(:ci_build) }
+ let_it_be(:artifact) { create(:ci_job_artifact, :codequality, expire_at: 1.hour.from_now, job: job) }
+
+ let(:options) { { request: double } }
let(:entity) do
- described_class.new(artifact, request: double)
+ described_class.represent(artifact, options)
end
describe '#as_json' do
@@ -46,5 +48,15 @@ RSpec.describe BuildArtifactEntity do
expect(subject[:browse_path]).to be_present
end
end
+
+ context 'when project is specified in options' do
+ let(:options) { super().merge(project: job.project) }
+
+ it 'doesnt get a project from the artifact' do
+ expect(artifact).not_to receive(:project)
+
+ subject
+ end
+ end
end
end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 35f4b97df0a..98c85234fe7 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -71,19 +71,21 @@ RSpec.describe Ci::CreatePipelineService do
end
it 'increments the prometheus counter' do
- expect(Gitlab::Metrics).to receive(:counter)
- .with(:pipelines_created_total, "Counter of pipelines created")
- .and_call_original
- allow(Gitlab::Metrics).to receive(:counter).and_call_original # allow other counters
+ counter = spy('pipeline created counter')
+
+ allow(Gitlab::Ci::Pipeline::Metrics)
+ .to receive(:pipelines_created_counter).and_return(counter)
pipeline
+
+ expect(counter).to have_received(:increment)
end
it 'records pipeline size in a prometheus histogram' do
histogram = spy('pipeline size histogram')
allow(Gitlab::Ci::Pipeline::Metrics)
- .to receive(:new).and_return(histogram)
+ .to receive(:pipeline_size_histogram).and_return(histogram)
execute_service
@@ -580,6 +582,13 @@ RSpec.describe Ci::CreatePipelineService do
it_behaves_like 'a failed pipeline'
+ it 'increments the error metric' do
+ stub_ci_pipeline_yaml_file(ci_yaml)
+
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { execute_service }.to change { counter.get(reason: 'config_error') }.by(1)
+ end
+
context 'when receive git commit' do
before do
allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index e02536fd07f..254bd19c808 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -10,6 +10,14 @@ RSpec.describe Ci::ProcessPipelineService do
create(:ci_empty_pipeline, ref: 'master', project: project)
end
+ let(:pipeline_processing_events_counter) { double(increment: true) }
+ let(:legacy_update_jobs_counter) { double(increment: true) }
+
+ let(:metrics) do
+ double(pipeline_processing_events_counter: pipeline_processing_events_counter,
+ legacy_update_jobs_counter: legacy_update_jobs_counter)
+ end
+
subject { described_class.new(pipeline) }
before do
@@ -17,22 +25,13 @@ RSpec.describe Ci::ProcessPipelineService do
stub_not_protect_default_branch
project.add_developer(user)
+
+ allow(subject).to receive(:metrics).and_return(metrics)
end
describe 'processing events counter' do
- let(:metrics) { double('pipeline metrics') }
- let(:counter) { double('events counter') }
-
- before do
- allow(subject)
- .to receive(:metrics).and_return(metrics)
- allow(metrics)
- .to receive(:pipeline_processing_events_counter)
- .and_return(counter)
- end
-
it 'increments processing events counter' do
- expect(counter).to receive(:increment)
+ expect(pipeline_processing_events_counter).to receive(:increment)
subject.execute
end
@@ -64,33 +63,22 @@ RSpec.describe Ci::ProcessPipelineService do
expect(all_builds.retried).to contain_exactly(build_retried)
end
- context 'counter ci_legacy_update_jobs_as_retried_total' do
- let(:counter) { double(increment: true) }
+ it 'increments the counter' do
+ expect(legacy_update_jobs_counter).to receive(:increment)
+ subject.execute
+ end
+
+ context 'when the previous build has already retried column true' do
before do
- allow(Gitlab::Metrics).to receive(:counter).and_call_original
- allow(Gitlab::Metrics).to receive(:counter)
- .with(:ci_legacy_update_jobs_as_retried_total, anything)
- .and_return(counter)
+ build_retried.update_columns(retried: true)
end
- it 'increments the counter' do
- expect(counter).to receive(:increment)
+ it 'does not increment the counter' do
+ expect(legacy_update_jobs_counter).not_to receive(:increment)
subject.execute
end
-
- context 'when the previous build has already retried column true' do
- before do
- build_retried.update_columns(retried: true)
- end
-
- it 'does not increment the counter' do
- expect(counter).not_to receive(:increment)
-
- subject.execute
- end
- end
end
end
diff --git a/spec/support/shared_examples/features/sidebar_shared_examples.rb b/spec/support/shared_examples/features/sidebar_shared_examples.rb
index 558536a80c6..429efbe6ba0 100644
--- a/spec/support/shared_examples/features/sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar_shared_examples.rb
@@ -8,19 +8,19 @@ RSpec.shared_examples 'issue boards sidebar' do
end
it 'shows sidebar when clicking issue' do
- expect(page).to have_selector('.issue-boards-sidebar')
+ expect(page).to have_selector('[data-testid="issue-boards-sidebar"]')
end
it 'closes sidebar when clicking issue' do
- expect(page).to have_selector('.issue-boards-sidebar')
+ expect(page).to have_selector('[data-testid="issue-boards-sidebar"]')
first_card.click
- expect(page).not_to have_selector('.issue-boards-sidebar')
+ expect(page).not_to have_selector('[data-testid="issue-boards-sidebar"]')
end
it 'shows issue details when sidebar is open', :aggregate_failures do
- page.within('.issue-boards-sidebar') do
+ page.within('[data-testid="issue-boards-sidebar"]') do
expect(page).to have_content(issue.title)
expect(page).to have_content(issue.to_reference)
end
@@ -28,7 +28,7 @@ RSpec.shared_examples 'issue boards sidebar' do
context 'when clicking close button' do
before do
- find("[data-testid='sidebar-drawer'] .gl-drawer-close-button").click
+ find('[data-testid="issue-boards-sidebar"] .gl-drawer-close-button').click
end
it 'unhighlights the active issue card' do
@@ -37,7 +37,7 @@ RSpec.shared_examples 'issue boards sidebar' do
end
it 'closes sidebar when clicking close button' do
- expect(page).not_to have_selector('.issue-boards-sidebar')
+ expect(page).not_to have_selector('[data-testid="issue-boards-sidebar"]')
end
end