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--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/boards/boards_util.js33
-rw-r--r--app/assets/javascripts/boards/components/board_card_layout.vue2
-rw-r--r--app/assets/javascripts/boards/models/issue.js2
-rw-r--r--app/assets/javascripts/boards/queries/issue.fragment.graphql1
-rw-r--r--app/assets/javascripts/boards/queries/issue_move_list.mutation.graphql28
-rw-r--r--app/assets/javascripts/boards/stores/actions.js39
-rw-r--r--app/assets/javascripts/boards/stores/mutation_types.js6
-rw-r--r--app/assets/javascripts/boards/stores/mutations.js48
-rw-r--r--app/assets/javascripts/design_management/graphql/fragments/design_list.fragment.graphql5
-rw-r--r--app/assets/javascripts/design_management/utils/cache_update.js33
-rw-r--r--app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue67
-rw-r--r--app/assets/javascripts/pages/admin/projects/index/index.js21
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue27
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss20
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/controllers/concerns/issuable_actions.rb2
-rw-r--r--app/models/ci/build_pending_state.rb9
-rw-r--r--app/models/clusters/instance.rb2
-rw-r--r--app/views/admin/projects/_projects.html.haml5
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml2
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml2
-rw-r--r--app/views/projects/cycle_analytics/_overview.html.haml15
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml2
-rw-r--r--app/views/shared/_help_dropdown_forum_link.html.haml2
-rw-r--r--app/views/shared/notes/_note.html.haml2
-rwxr-xr-xbin/feature-flag6
-rw-r--r--changelogs/unreleased/225950-replace-fa-trash-o-with-gitlab-svg-remove.yml5
-rw-r--r--changelogs/unreleased/feature-gb-add-ci-build-state-table.yml5
-rw-r--r--changelogs/unreleased/retention-add-forum-link-to-help-menu.yml5
-rw-r--r--db/migrate/20200904131544_create_ci_build_pending_states.rb25
-rw-r--r--db/schema_migrations/202009041315441
-rw-r--r--db/structure.sql29
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql6
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json6
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/development/feature_flags/development.md52
-rw-r--r--lib/feature/shared.rb13
-rw-r--r--lib/gitlab/background_migration/populate_vulnerability_historical_statistics.rb14
-rw-r--r--locale/gitlab.pot22
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb4
-rw-r--r--spec/frontend/boards/mock_data.js40
-rw-r--r--spec/frontend/boards/stores/actions_spec.js112
-rw-r--r--spec/frontend/boards/stores/mutations_spec.js89
-rw-r--r--spec/frontend/design_management/mock_data/apollo_mock.js18
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js9
-rw-r--r--spec/services/notification_service_spec.rb65
-rw-r--r--spec/workers/create_pipeline_worker_spec.rb4
-rw-r--r--spec/workers/new_issue_worker_spec.rb14
-rw-r--r--spec/workers/new_merge_request_worker_spec.rb16
50 files changed, 745 insertions, 198 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 6e89702d316..02fe0a51d0b 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-b9703797a48c5cff1adf84a152c4024cff9b35ed
+47f676eea28871563414671e1016fb28b1b3e167
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js
index 652f8a8a107..5c8df94ca90 100644
--- a/app/assets/javascripts/boards/boards_util.js
+++ b/app/assets/javascripts/boards/boards_util.js
@@ -1,17 +1,28 @@
+import { sortBy } from 'lodash';
import ListIssue from 'ee_else_ce/boards/models/issue';
+import { ListType } from './constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
export function getMilestone() {
return null;
}
+export function formatIssue(issue) {
+ return new ListIssue({
+ ...issue,
+ labels: issue.labels?.nodes || [],
+ assignees: issue.assignees?.nodes || [],
+ });
+}
+
export function formatListIssues(listIssues) {
const issues = {};
const listData = listIssues.nodes.reduce((map, list) => {
+ const sortedIssues = sortBy(list.issues.nodes, 'relativePosition');
return {
...map,
- [list.id]: list.issues.nodes.map(i => {
+ [list.id]: sortedIssues.map(i => {
const id = getIdFromGraphQLId(i.id);
const listIssue = new ListIssue({
@@ -35,7 +46,27 @@ export function fullBoardId(boardId) {
return `gid://gitlab/Board/${boardId}`;
}
+export function moveIssueListHelper(issue, fromList, toList) {
+ if (toList.type === ListType.label) {
+ issue.addLabel(toList.label);
+ }
+ if (fromList && fromList.type === ListType.label) {
+ issue.removeLabel(fromList.label);
+ }
+
+ if (toList.type === ListType.assignee) {
+ issue.addAssignee(toList.assignee);
+ }
+ if (fromList && fromList.type === ListType.assignee) {
+ issue.removeAssignee(fromList.assignee);
+ }
+
+ return issue;
+}
+
export default {
getMilestone,
+ formatIssue,
formatListIssues,
+ fullBoardId,
};
diff --git a/app/assets/javascripts/boards/components/board_card_layout.vue b/app/assets/javascripts/boards/components/board_card_layout.vue
index f3ce25b4696..e3b57d5a7f7 100644
--- a/app/assets/javascripts/boards/components/board_card_layout.vue
+++ b/app/assets/javascripts/boards/components/board_card_layout.vue
@@ -95,6 +95,8 @@ export default {
}"
:index="index"
:data-issue-id="issue.id"
+ :data-issue-iid="issue.iid"
+ :data-issue-path="issue.referencePath"
data-testid="board_card"
class="board-card p-3 rounded"
@mousedown="mouseDown"
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 98eac35b2ed..822e6d62ab3 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -15,7 +15,7 @@ class ListIssue {
this.labels = [];
this.assignees = [];
this.selected = false;
- this.position = obj.position || obj.relative_position || Infinity;
+ this.position = obj.position || obj.relative_position || obj.relativePosition || Infinity;
this.isFetching = {
subscriptions: true,
};
diff --git a/app/assets/javascripts/boards/queries/issue.fragment.graphql b/app/assets/javascripts/boards/queries/issue.fragment.graphql
index 89d56b895a4..21b52766190 100644
--- a/app/assets/javascripts/boards/queries/issue.fragment.graphql
+++ b/app/assets/javascripts/boards/queries/issue.fragment.graphql
@@ -12,6 +12,7 @@ fragment IssueNode on Issue {
webUrl
subscribed
blocked
+ relativePosition
epic {
id
}
diff --git a/app/assets/javascripts/boards/queries/issue_move_list.mutation.graphql b/app/assets/javascripts/boards/queries/issue_move_list.mutation.graphql
new file mode 100644
index 00000000000..1b5f9564e59
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/issue_move_list.mutation.graphql
@@ -0,0 +1,28 @@
+#import "./issue.fragment.graphql"
+
+mutation IssueMoveList(
+ $projectPath: ID!
+ $iid: String!
+ $boardId: ID!
+ $fromListId: ID
+ $toListId: ID
+ $moveBeforeId: ID
+ $moveAfterId: ID
+) {
+ issueMoveList(
+ input: {
+ projectPath: $projectPath
+ iid: $iid
+ boardId: $boardId
+ fromListId: $fromListId
+ toListId: $toListId
+ moveBeforeId: $moveBeforeId
+ moveAfterId: $moveAfterId
+ }
+ ) {
+ issue {
+ ...IssueNode
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js
index 8d9b58f71cb..f8767211abd 100644
--- a/app/assets/javascripts/boards/stores/actions.js
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -15,6 +15,7 @@ import projectBoardQuery from '../queries/project_board.query.graphql';
import groupBoardQuery from '../queries/group_board.query.graphql';
import createBoardListMutation from '../queries/board_list_create.mutation.graphql';
import updateBoardListMutation from '../queries/board_list_update.mutation.graphql';
+import issueMoveListMutation from '../queries/issue_move_list.mutation.graphql';
const notImplemented = () => {
/* eslint-disable-next-line @gitlab/require-i18n-strings */
@@ -227,8 +228,42 @@ export default {
.catch(() => commit(types.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE));
},
- moveIssue: () => {
- notImplemented();
+ moveIssue: (
+ { state, commit },
+ { issueId, issueIid, issuePath, fromListId, toListId, moveBeforeId, moveAfterId },
+ ) => {
+ const originalIssue = state.issues[issueId];
+ const fromList = state.issuesByListId[fromListId];
+ const originalIndex = fromList.indexOf(Number(issueId));
+ commit(types.MOVE_ISSUE, { originalIssue, fromListId, toListId, moveBeforeId, moveAfterId });
+
+ const { boardId } = state.endpoints;
+ const [groupPath, project] = issuePath.split(/[/#]/);
+
+ gqlClient
+ .mutate({
+ mutation: issueMoveListMutation,
+ variables: {
+ projectPath: `${groupPath}/${project}`,
+ boardId: fullBoardId(boardId),
+ iid: issueIid,
+ fromListId: getIdFromGraphQLId(fromListId),
+ toListId: getIdFromGraphQLId(toListId),
+ moveBeforeId,
+ moveAfterId,
+ },
+ })
+ .then(({ data }) => {
+ if (data?.issueMoveList?.errors.length) {
+ commit(types.MOVE_ISSUE_FAILURE, { originalIssue, fromListId, toListId, originalIndex });
+ } else {
+ const issue = data.issueMoveList?.issue;
+ commit(types.MOVE_ISSUE_SUCCESS, { issue });
+ }
+ })
+ .catch(() =>
+ commit(types.MOVE_ISSUE_FAILURE, { originalIssue, fromListId, toListId, originalIndex }),
+ );
},
createNewIssue: () => {
diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js
index a3b84108cb3..5a3d62dc703 100644
--- a/app/assets/javascripts/boards/stores/mutation_types.js
+++ b/app/assets/javascripts/boards/stores/mutation_types.js
@@ -18,9 +18,9 @@ export const RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE = 'RECEIVE_ISSUES_FOR_ALL_LIST
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 REQUEST_MOVE_ISSUE = 'REQUEST_MOVE_ISSUE';
-export const RECEIVE_MOVE_ISSUE_SUCCESS = 'RECEIVE_MOVE_ISSUE_SUCCESS';
-export const RECEIVE_MOVE_ISSUE_ERROR = 'RECEIVE_MOVE_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 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';
diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js
index f25c339836f..a03c541ada5 100644
--- a/app/assets/javascripts/boards/stores/mutations.js
+++ b/app/assets/javascripts/boards/stores/mutations.js
@@ -1,7 +1,9 @@
import Vue from 'vue';
import { sortBy, pull } from 'lodash';
+import { formatIssue, moveIssueListHelper } from '../boards_util';
import * as mutationTypes from './mutation_types';
import { __ } from '~/locale';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
const notImplemented = () => {
/* eslint-disable-next-line @gitlab/require-i18n-strings */
@@ -12,6 +14,18 @@ const removeIssueFromList = (state, listId, issueId) => {
Vue.set(state.issuesByListId, listId, pull(state.issuesByListId[listId], issueId));
};
+const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atIndex }) => {
+ const listIssues = state.issuesByListId[listId];
+ let newIndex = atIndex || 0;
+ if (moveBeforeId) {
+ newIndex = listIssues.indexOf(moveBeforeId) + 1;
+ } else if (moveAfterId) {
+ newIndex = listIssues.indexOf(moveAfterId);
+ }
+ listIssues.splice(newIndex, 0, issueId);
+ Vue.set(state.issuesByListId, listId, listIssues);
+};
+
export default {
[mutationTypes.SET_INITIAL_BOARD_DATA](state, data) {
const { boardType, disabled, showPromotion, ...endpoints } = data;
@@ -111,16 +125,38 @@ export default {
notImplemented();
},
- [mutationTypes.REQUEST_MOVE_ISSUE]: () => {
- notImplemented();
+ [mutationTypes.MOVE_ISSUE]: (
+ state,
+ { originalIssue, fromListId, toListId, moveBeforeId, moveAfterId },
+ ) => {
+ const fromList = state.boardLists.find(l => l.id === fromListId);
+ const toList = state.boardLists.find(l => l.id === toListId);
+
+ const issue = moveIssueListHelper(originalIssue, fromList, toList);
+ Vue.set(state.issues, issue.id, issue);
+
+ removeIssueFromList(state, fromListId, issue.id);
+ addIssueToList({ state, listId: toListId, issueId: issue.id, moveBeforeId, moveAfterId });
},
- [mutationTypes.RECEIVE_MOVE_ISSUE_SUCCESS]: () => {
- notImplemented();
+ [mutationTypes.MOVE_ISSUE_SUCCESS]: (state, { issue }) => {
+ const issueId = getIdFromGraphQLId(issue.id);
+ Vue.set(state.issues, issueId, formatIssue({ ...issue, id: issueId }));
},
- [mutationTypes.RECEIVE_MOVE_ISSUE_ERROR]: () => {
- notImplemented();
+ [mutationTypes.MOVE_ISSUE_FAILURE]: (
+ state,
+ { originalIssue, fromListId, toListId, originalIndex },
+ ) => {
+ state.error = __('An error occurred while moving the issue. Please try again.');
+ Vue.set(state.issues, originalIssue.id, originalIssue);
+ removeIssueFromList(state, toListId, originalIssue.id);
+ addIssueToList({
+ state,
+ listId: fromListId,
+ issueId: originalIssue.id,
+ atIndex: originalIndex,
+ });
},
[mutationTypes.REQUEST_UPDATE_ISSUE]: () => {
diff --git a/app/assets/javascripts/design_management/graphql/fragments/design_list.fragment.graphql b/app/assets/javascripts/design_management/graphql/fragments/design_list.fragment.graphql
index bc3132f9b42..9bd70e7e886 100644
--- a/app/assets/javascripts/design_management/graphql/fragments/design_list.fragment.graphql
+++ b/app/assets/javascripts/design_management/graphql/fragments/design_list.fragment.graphql
@@ -5,4 +5,9 @@ fragment DesignListItem on Design {
notesCount
image
imageV432x230
+ currentUserTodos(state: pending) {
+ nodes {
+ id
+ }
+ }
}
diff --git a/app/assets/javascripts/design_management/utils/cache_update.js b/app/assets/javascripts/design_management/utils/cache_update.js
index 2ffbae8afe0..ff41136fd54 100644
--- a/app/assets/javascripts/design_management/utils/cache_update.js
+++ b/app/assets/javascripts/design_management/utils/cache_update.js
@@ -190,25 +190,44 @@ const moveDesignInStore = (store, designManagementMove, query) => {
};
export const addPendingTodoToStore = (store, pendingTodo, query, queryVariables) => {
- const data = store.readQuery({
+ const sourceData = store.readQuery({
query,
variables: queryVariables,
});
- // TODO produce new version of data that includes the new pendingTodo.
- // This is only possible after BE MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40555
+ const data = produce(sourceData, draftData => {
+ const design = extractDesign(draftData);
+ const existingTodos = design.currentUserTodos?.nodes || [];
+ const newTodoNodes = [...existingTodos, { ...pendingTodo, __typename: 'Todo' }];
+
+ if (!design.currentUserTodos) {
+ design.currentUserTodos = {
+ __typename: 'TodoConnection',
+ nodes: newTodoNodes,
+ };
+ } else {
+ design.currentUserTodos.nodes = newTodoNodes;
+ }
+ });
store.writeQuery({ query, variables: queryVariables, data });
};
-export const deletePendingTodoFromStore = (store, pendingTodo, query, queryVariables) => {
- const data = store.readQuery({
+export const deletePendingTodoFromStore = (store, todoMarkDone, query, queryVariables) => {
+ const sourceData = store.readQuery({
query,
variables: queryVariables,
});
- // TODO produce new version of data without the pendingTodo.
- // This is only possible after BE MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40555
+ const {
+ todo: { id: todoId },
+ } = todoMarkDone;
+ const data = produce(sourceData, draftData => {
+ const design = extractDesign(draftData);
+ const existingTodos = design.currentUserTodos?.nodes || [];
+
+ design.currentUserTodos.nodes = existingTodos.filter(({ id }) => id !== todoId);
+ });
store.writeQuery({ query, variables: queryVariables, data });
};
diff --git a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
index c14ea7bd1d2..b92fc8d125d 100644
--- a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
+++ b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
@@ -1,12 +1,11 @@
<script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlSafeHtmlDirective as SafeHtml, GlModal } from '@gitlab/ui';
import { escape } from 'lodash';
-import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
-import { s__, sprintf } from '~/locale';
+import { __, s__, sprintf } from '~/locale';
export default {
components: {
- DeprecatedModal,
+ GlModal,
},
directives: {
SafeHtml,
@@ -66,51 +65,57 @@ export default {
false,
);
},
- primaryButtonLabel() {
- return s__('AdminProjects|Delete project');
- },
canSubmit() {
return this.enteredProjectName === this.projectName;
},
+ primaryProps() {
+ return {
+ text: s__('Delete project'),
+ attributes: [{ variant: 'danger' }, { category: 'primary' }, { disabled: !this.canSubmit }],
+ };
+ },
},
methods: {
onCancel() {
this.enteredProjectName = '';
},
onSubmit() {
+ if (!this.canSubmit) {
+ return;
+ }
this.$refs.form.submit();
this.enteredProjectName = '';
},
},
+ cancelProps: {
+ text: __('Cancel'),
+ },
};
</script>
<template>
- <deprecated-modal
- id="delete-project-modal"
+ <gl-modal
+ modal-id="delete-project-modal"
:title="title"
- :text="text"
- :primary-button-label="primaryButtonLabel"
- :submit-disabled="!canSubmit"
- kind="danger"
- @submit="onSubmit"
+ :action-primary="primaryProps"
+ :action-cancel="$options.cancelProps"
+ :ok-disabled="!canSubmit"
+ @primary="onSubmit"
@cancel="onCancel"
>
- <template #body="props">
- <p v-safe-html="props.text"></p>
- <p v-safe-html="confirmationTextLabel"></p>
- <form ref="form" :action="deleteProjectUrl" method="post">
- <input ref="method" type="hidden" name="_method" value="delete" />
- <input :value="csrfToken" type="hidden" name="authenticity_token" />
- <input
- v-model="enteredProjectName"
- name="projectName"
- class="form-control"
- type="text"
- aria-labelledby="input-label"
- autocomplete="off"
- />
- </form>
- </template>
- </deprecated-modal>
+ <p v-safe-html="text"></p>
+ <p v-safe-html="confirmationTextLabel"></p>
+ <form ref="form" :action="deleteProjectUrl" method="post">
+ <input ref="method" type="hidden" name="_method" value="delete" />
+ <input :value="csrfToken" type="hidden" name="authenticity_token" />
+ <input
+ v-model="enteredProjectName"
+ name="projectName"
+ class="form-control"
+ type="text"
+ aria-labelledby="input-label"
+ autocomplete="off"
+ />
+ </form>
+ </gl-modal>
</template>
diff --git a/app/assets/javascripts/pages/admin/projects/index/index.js b/app/assets/javascripts/pages/admin/projects/index/index.js
index 6fa8760545d..ebb1a74e970 100644
--- a/app/assets/javascripts/pages/admin/projects/index/index.js
+++ b/app/assets/javascripts/pages/admin/projects/index/index.js
@@ -1,4 +1,3 @@
-import $ from 'jquery';
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
@@ -17,6 +16,18 @@ document.addEventListener('DOMContentLoaded', () => {
deleteProjectUrl: '',
projectName: '',
},
+ mounted() {
+ const deleteProjectButtons = document.querySelectorAll('.delete-project-button');
+ deleteProjectButtons.forEach(button => {
+ button.addEventListener('click', () => {
+ const buttonProps = button.dataset;
+ deleteModal.deleteProjectUrl = buttonProps.deleteProjectUrl;
+ deleteModal.projectName = buttonProps.projectName;
+
+ this.$root.$emit('bv::show::modal', 'delete-project-modal');
+ });
+ });
+ },
render(createElement) {
return createElement(deleteProjectModal, {
props: {
@@ -27,12 +38,4 @@ document.addEventListener('DOMContentLoaded', () => {
});
},
});
-
- $(document).on('shown.bs.modal', event => {
- if (event.relatedTarget.classList.contains('delete-project-button')) {
- const buttonProps = event.relatedTarget.dataset;
- deleteModal.deleteProjectUrl = buttonProps.deleteProjectUrl;
- deleteModal.projectName = buttonProps.projectName;
- }
- });
});
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue
index 32b0121f6da..434aabc3df9 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue
@@ -28,21 +28,24 @@ export default {
<template>
<div class="dropdown-page-two dropdown-new-label">
- <div class="dropdown-title">
+ <div
+ class="dropdown-title gl-display-flex gl-justify-content-space-between gl-align-items-center"
+ >
<gl-button
:aria-label="__('Go back')"
category="tertiary"
- class="dropdown-title-button dropdown-menu-back"
+ class="dropdown-menu-back"
icon="arrow-left"
+ size="small"
/>
{{ headerTitle }}
- <button
+ <gl-button
:aria-label="__('Close')"
- type="button"
- class="dropdown-title-button dropdown-menu-close"
- >
- <i aria-hidden="true" class="fa fa-times dropdown-menu-close-icon" data-hidden="true"> </i>
- </button>
+ category="tertiary"
+ class="dropdown-menu-close"
+ icon="close"
+ size="small"
+ />
</div>
<div class="dropdown-content">
<div class="dropdown-labels-error js-label-error"></div>
@@ -77,12 +80,12 @@ export default {
/>
</div>
<div class="clearfix">
- <button type="button" class="btn btn-primary float-left js-new-label-btn disabled">
+ <gl-button category="secondary" class="float-left js-new-label-btn disabled">
{{ __('Create') }}
- </button>
- <button type="button" class="btn btn-default float-right js-cancel-label-btn">
+ </gl-button>
+ <gl-button category="secondary" class="float-right js-cancel-label-btn">
{{ __('Cancel') }}
- </button>
+ </gl-button>
</div>
</div>
</div>
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index b97709e140f..68d9694905a 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -360,23 +360,3 @@
}
}
}
-
-.cycle-analytics-overview {
- padding-top: 100px;
-
- .overview-details {
- display: flex;
- align-items: center;
- }
-
- .overview-image {
- text-align: right;
- }
-
- .overview-icon {
- svg {
- width: 365px;
- height: 227px;
- }
- }
-}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 9dbe1ab9214..bfb464f1ff2 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -119,11 +119,11 @@
.assignee {
.merge-icon {
- color: $orange-500;
+ color: $orange-400;
position: absolute;
bottom: 0;
right: 0;
- text-shadow: -1px -1px 0 $white, 1px -1px 0 $white, -1px 1px 0 $white, 1px 1px 0 $white;
+ text-shadow: -1px -1px 2px $white, 1px -1px 2px $white, -1px 1px 2px $white, 1px 1px 2px $white;
}
}
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index c4dbce00593..a1a2740cde2 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -9,7 +9,7 @@ module IssuableActions
before_action :check_destroy_confirmation!, only: :destroy
before_action :authorize_admin_issuable!, only: :bulk_update
before_action only: :show do
- push_frontend_feature_flag(:scoped_labels, default_enabled: true)
+ push_frontend_feature_flag(:scoped_labels, type: :licensed, default_enabled: true)
end
before_action do
push_frontend_feature_flag(:not_issuable_queries, @project, default_enabled: true)
diff --git a/app/models/ci/build_pending_state.rb b/app/models/ci/build_pending_state.rb
new file mode 100644
index 00000000000..1ca81537694
--- /dev/null
+++ b/app/models/ci/build_pending_state.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Ci::BuildPendingState < ApplicationRecord
+ extend Gitlab::Ci::Model
+
+ belongs_to :build, class_name: 'Ci::Build', foreign_key: :build_id
+
+ validates :build, presence: true
+end
diff --git a/app/models/clusters/instance.rb b/app/models/clusters/instance.rb
index 8c9d9ab9ab1..94fadace01c 100644
--- a/app/models/clusters/instance.rb
+++ b/app/models/clusters/instance.rb
@@ -7,7 +7,7 @@ module Clusters
end
def feature_available?(feature)
- ::Feature.enabled?(feature, default_enabled: true)
+ ::Feature.enabled?(feature, type: :licensed, default_enabled: true)
end
def flipper_id
diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml
index f842ab2d009..44317eb7f6e 100644
--- a/app/views/admin/projects/_projects.html.haml
+++ b/app/views/admin/projects/_projects.html.haml
@@ -5,10 +5,7 @@
%li.project-row{ class: ('no-description' if project.description.blank?) }
.controls
= link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn"
- %button.delete-project-button.btn.btn-danger{ data: { toggle: 'modal',
- target: '#delete-project-modal',
- delete_project_url: admin_project_path(project),
- project_name: project.name }, type: 'button' }
+ %button.delete-project-button.btn.btn-danger{ data: { delete_project_url: admin_project_path(project), project_name: project.name } }
= s_('AdminProjects|Delete')
.stats
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index 22c2be3b7da..dcc6cba8444 100644
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ b/app/views/layouts/header/_current_user_dropdown.html.haml
@@ -39,6 +39,8 @@
= link_to _("Help"), help_path
%li.d-md-none
= link_to _("Support"), support_url
+ %li.d-md-none
+ = render 'shared/help_dropdown_forum_link'
%li.d-md-none
= link_to _("Submit feedback"), "https://about.gitlab.com/submit-feedback"
- if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
index ad4e0f1f4b2..2b6cbc1c0ef 100644
--- a/app/views/layouts/header/_help_dropdown.html.haml
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -6,6 +6,8 @@
%li
= link_to _("Support"), support_url
%li
+ = render 'shared/help_dropdown_forum_link'
+ %li
%button.js-shortcuts-modal-trigger{ type: "button" }
= _("Keyboard shortcuts")
%span.text-secondary.float-right{ "aria-hidden": true }= '?'.html_safe
diff --git a/app/views/projects/cycle_analytics/_overview.html.haml b/app/views/projects/cycle_analytics/_overview.html.haml
deleted file mode 100644
index 2ca72b141be..00000000000
--- a/app/views/projects/cycle_analytics/_overview.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-.cycle-analytics-overview
- .container
- .row
- .col-md-10.offset-md-1
- .row.overview-details
- .col-md-6.overview-text
- %h4 Introducing Value Stream Analytics
- %p
- Value Stream Analytics (VSA) gives an overview of how much time it takes to go from idea to production in your project.
- To set up VSA, you must first define a production environment by setting up your CI and then deploy to production.
- %p
- %a.btn{ href: help_page_path('user/analytics/value_stream_analytics.md'), target: '_blank' } Read more
- .col-md-6.overview-image
- %span.overview-icon
- = custom_icon ('icon_cycle_analytics_overview')
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index 2f55cce70dc..5e72db22667 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -74,4 +74,4 @@
- if mirror.ssh_key_auth?
= clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key'), qa_selector: 'copy_public_key_button')
= render 'shared/remote_mirror_update_button', remote_mirror: mirror
- %button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
+ %button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= sprite_icon('remove')
diff --git a/app/views/shared/_help_dropdown_forum_link.html.haml b/app/views/shared/_help_dropdown_forum_link.html.haml
new file mode 100644
index 00000000000..351c875475a
--- /dev/null
+++ b/app/views/shared/_help_dropdown_forum_link.html.haml
@@ -0,0 +1,2 @@
+= link_to _("Community forum"), "https://forum.gitlab.com/", target: '_blank', class: 'text-nowrap',
+ rel: 'noopener noreferrer', data: { 'track_event': 'click_forum', 'track_property': 'question_menu' }
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index da665f17975..97ed2852871 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -76,4 +76,4 @@
= note.attachment_identifier
= link_to delete_attachment_project_note_path(note.project, note),
title: _('Delete this attachment'), method: :delete, remote: true, data: { confirm: _('Are you sure you want to remove the attachment?') }, class: 'danger js-note-attachment-delete' do
- = icon('trash-o', class: 'cred')
+ = sprite_icon('remove', css_class: 'cred')
diff --git a/bin/feature-flag b/bin/feature-flag
index ba86df9c06c..c01fb67131b 100755
--- a/bin/feature-flag
+++ b/bin/feature-flag
@@ -181,6 +181,10 @@ class FeatureFlagOptionParser
$stderr.puts "URL needs to start with https://"
end
end
+
+ def read_default_enabled(options)
+ TYPES.dig(options.type, :default_enabled)
+ end
end
end
@@ -226,7 +230,7 @@ class FeatureFlagCreator
'rollout_issue_url' => options.rollout_issue_url,
'group' => options.group.to_s,
'type' => options.type.to_s,
- 'default_enabled' => false
+ 'default_enabled' => FeatureFlagOptionParser.read_default_enabled(options)
).strip
end
diff --git a/changelogs/unreleased/225950-replace-fa-trash-o-with-gitlab-svg-remove.yml b/changelogs/unreleased/225950-replace-fa-trash-o-with-gitlab-svg-remove.yml
new file mode 100644
index 00000000000..90086279c28
--- /dev/null
+++ b/changelogs/unreleased/225950-replace-fa-trash-o-with-gitlab-svg-remove.yml
@@ -0,0 +1,5 @@
+---
+title: Replace fa-trash-o icons with GitLab SVG remove icon
+merge_request: 41748
+author:
+type: changed
diff --git a/changelogs/unreleased/feature-gb-add-ci-build-state-table.yml b/changelogs/unreleased/feature-gb-add-ci-build-state-table.yml
new file mode 100644
index 00000000000..9db08697bcf
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-add-ci-build-state-table.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce build states table / model / migration
+merge_request: 41585
+author:
+type: added
diff --git a/changelogs/unreleased/retention-add-forum-link-to-help-menu.yml b/changelogs/unreleased/retention-add-forum-link-to-help-menu.yml
new file mode 100644
index 00000000000..7c0c38ca857
--- /dev/null
+++ b/changelogs/unreleased/retention-add-forum-link-to-help-menu.yml
@@ -0,0 +1,5 @@
+---
+title: Add forum link to help menu
+merge_request: 41858
+author:
+type: added
diff --git a/db/migrate/20200904131544_create_ci_build_pending_states.rb b/db/migrate/20200904131544_create_ci_build_pending_states.rb
new file mode 100644
index 00000000000..2c21ce3ce32
--- /dev/null
+++ b/db/migrate/20200904131544_create_ci_build_pending_states.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class CreateCiBuildPendingStates < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ create_table :ci_build_pending_states do |t|
+ t.timestamps_with_timezone
+ t.references :build, index: { unique: true }, null: false, foreign_key: { to_table: :ci_builds, on_delete: :cascade }, type: :bigint
+ t.integer :state
+ t.integer :failure_reason
+ t.binary :trace_checksum
+ end
+ end
+ end
+
+ def down
+ with_lock_retries do
+ drop_table :ci_build_pending_states
+ end
+ end
+end
diff --git a/db/schema_migrations/20200904131544 b/db/schema_migrations/20200904131544
new file mode 100644
index 00000000000..62ff8ff56e7
--- /dev/null
+++ b/db/schema_migrations/20200904131544
@@ -0,0 +1 @@
+08c9c6e5cd19aac17de7fc639eaca5ddba3e8280452821b8a72f05dcde790feb \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 3aa653cb5a2..686775a5b11 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -9826,6 +9826,25 @@ CREATE SEQUENCE public.ci_build_needs_id_seq
ALTER SEQUENCE public.ci_build_needs_id_seq OWNED BY public.ci_build_needs.id;
+CREATE TABLE public.ci_build_pending_states (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ build_id bigint NOT NULL,
+ state integer,
+ failure_reason integer,
+ trace_checksum bytea
+);
+
+CREATE SEQUENCE public.ci_build_pending_states_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.ci_build_pending_states_id_seq OWNED BY public.ci_build_pending_states.id;
+
CREATE TABLE public.ci_build_report_results (
build_id bigint NOT NULL,
project_id bigint NOT NULL,
@@ -16963,6 +16982,8 @@ ALTER TABLE ONLY public.chat_teams ALTER COLUMN id SET DEFAULT nextval('public.c
ALTER TABLE ONLY public.ci_build_needs ALTER COLUMN id SET DEFAULT nextval('public.ci_build_needs_id_seq'::regclass);
+ALTER TABLE ONLY public.ci_build_pending_states ALTER COLUMN id SET DEFAULT nextval('public.ci_build_pending_states_id_seq'::regclass);
+
ALTER TABLE ONLY public.ci_build_report_results ALTER COLUMN build_id SET DEFAULT nextval('public.ci_build_report_results_build_id_seq'::regclass);
ALTER TABLE ONLY public.ci_build_trace_chunks ALTER COLUMN id SET DEFAULT nextval('public.ci_build_trace_chunks_id_seq'::regclass);
@@ -17924,6 +17945,9 @@ ALTER TABLE public.merge_request_diffs
ALTER TABLE ONLY public.ci_build_needs
ADD CONSTRAINT ci_build_needs_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY public.ci_build_pending_states
+ ADD CONSTRAINT ci_build_pending_states_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY public.ci_build_report_results
ADD CONSTRAINT ci_build_report_results_pkey PRIMARY KEY (build_id);
@@ -19370,6 +19394,8 @@ CREATE UNIQUE INDEX index_chat_teams_on_namespace_id ON public.chat_teams USING
CREATE UNIQUE INDEX index_ci_build_needs_on_build_id_and_name ON public.ci_build_needs USING btree (build_id, name);
+CREATE UNIQUE INDEX index_ci_build_pending_states_on_build_id ON public.ci_build_pending_states USING btree (build_id);
+
CREATE INDEX index_ci_build_report_results_on_project_id ON public.ci_build_report_results USING btree (project_id);
CREATE UNIQUE INDEX index_ci_build_trace_chunks_on_build_id_and_chunk_index ON public.ci_build_trace_chunks USING btree (build_id, chunk_index);
@@ -22285,6 +22311,9 @@ ALTER TABLE ONLY public.project_deploy_tokens
ALTER TABLE ONLY public.packages_conan_file_metadata
ADD CONSTRAINT fk_rails_0afabd9328 FOREIGN KEY (package_file_id) REFERENCES public.packages_package_files(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.ci_build_pending_states
+ ADD CONSTRAINT fk_rails_0bbbfeaf9d FOREIGN KEY (build_id) REFERENCES public.ci_builds(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.operations_user_lists
ADD CONSTRAINT fk_rails_0c716e079b FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 6f3897d1574..18d35c3e114 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -3048,7 +3048,7 @@ type DastScannerProfile {
profileName: String
"""
- The maximum number of seconds allowed for the spider to traverse the site
+ The maximum number of minutes allowed for the spider to traverse the site
"""
spiderTimeout: Int
@@ -3098,7 +3098,7 @@ input DastScannerProfileCreateInput {
profileName: String!
"""
- The maximum number of seconds allowed for the spider to traverse the site.
+ The maximum number of minutes allowed for the spider to traverse the site.
"""
spiderTimeout: Int
@@ -3213,7 +3213,7 @@ input DastScannerProfileUpdateInput {
profileName: String!
"""
- The maximum number of seconds allowed for the spider to traverse the site.
+ The maximum number of minutes allowed for the spider to traverse the site.
"""
spiderTimeout: Int!
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 44405c21f6a..e28c0201e7e 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -8289,7 +8289,7 @@
},
{
"name": "spiderTimeout",
- "description": "The maximum number of seconds allowed for the spider to traverse the site",
+ "description": "The maximum number of minutes allowed for the spider to traverse the site",
"args": [
],
@@ -8426,7 +8426,7 @@
},
{
"name": "spiderTimeout",
- "description": "The maximum number of seconds allowed for the spider to traverse the site.",
+ "description": "The maximum number of minutes allowed for the spider to traverse the site.",
"type": {
"kind": "SCALAR",
"name": "Int",
@@ -8747,7 +8747,7 @@
},
{
"name": "spiderTimeout",
- "description": "The maximum number of seconds allowed for the spider to traverse the site.",
+ "description": "The maximum number of minutes allowed for the spider to traverse the site.",
"type": {
"kind": "NON_NULL",
"name": null,
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 1089bfb6aad..086a2dac6d0 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -519,7 +519,7 @@ Represents a DAST scanner profile.
| `globalId` | DastScannerProfileID! | ID of the DAST scanner profile |
| `id` **{warning-solid}** | ID! | **Deprecated:** Use `global_id`. Deprecated in 13.4 |
| `profileName` | String | Name of the DAST scanner profile |
-| `spiderTimeout` | Int | The maximum number of seconds allowed for the spider to traverse the site |
+| `spiderTimeout` | Int | The maximum number of minutes allowed for the spider to traverse the site |
| `targetTimeout` | Int | The maximum number of seconds allowed for the site under test to respond to a request |
## DastScannerProfileCreatePayload
diff --git a/doc/development/feature_flags/development.md b/doc/development/feature_flags/development.md
index bf7fee90600..b7923cfc268 100644
--- a/doc/development/feature_flags/development.md
+++ b/doc/development/feature_flags/development.md
@@ -61,6 +61,30 @@ Feature.disabled?(:my_ops_flag, project, type: ops)
push_frontend_feature_flag(:my_ops_flag, project, type: :ops)
```
+### `licensed` type
+
+`licensed` feature flags are used to temporarily disable licensed features. There
+should be a one-to-one mapping of `licensed` feature flags to licensed features.
+
+`licensed` feature flags must be `default_enabled: true`, because that's the only
+supported option in the current implementation. This is under development as per
+the [related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/218667.
+
+The `licensed` type has a dedicated set of functions to check if a licensed
+feature is available for a project or namespace. This check validates
+if the license is assigned to the namespace and feature flag itself.
+The `licensed` feature flag has the same name as a licensed feature name:
+
+```ruby
+# Good: checks if feature flag is enabled
+project.feature_available?(:my_licensed_feature)
+namespace.feature_available?(:my_licensed_feature)
+
+# Bad: licensed flag must be accessed via `feature_available?`
+Feature.enabled?(:my_licensed_feature, type: :licensed)
+push_frontend_feature_flag(:my_licensed_feature, type: :licensed)
+```
+
## Feature flag definition and validation
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229161) in GitLab 13.3.
@@ -290,13 +314,17 @@ The [`Project#feature_available?`](https://gitlab.com/gitlab-org/gitlab/blob/4cc
[`License.feature_available?`](https://gitlab.com/gitlab-org/gitlab/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/license.rb#L293-300) (EE) methods all implicitly check for
a by default enabled feature flag with the same name as the provided argument.
-You'd still want to use an explicit `Feature.enabled?` check if your new feature
-isn't gated by a License or Plan.
-
**An important side-effect of the implicit feature flags mentioned above is that
unless the feature is explicitly disabled or limited to a percentage of users,
the feature flag check will default to `true`.**
+NOTE: **Note:**
+Due to limitations with `feature_available?`, the YAML definition for `licensed` feature
+flags accepts only `default_enabled: true`. This is under development as per the
+[related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/218667).
+
+#### Alpha/beta licensed feature flags
+
This is relevant when developing the feature using
[several smaller merge requests](https://about.gitlab.com/handbook/values/#make-small-merge-requests), or when the feature is considered to be an
[alpha or beta](https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha-beta-ga), and
@@ -311,6 +339,24 @@ GitLab.com and self-managed instances, you should use the
method, according to our [definitions](https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha-beta-ga). This ensures the feature is disabled unless the feature flag is
_explicitly_ enabled.
+CAUTION: **Caution:**
+If `alpha_feature_available?` or `beta_feature_available?` is used, the YAML definition
+for the feature flag must use `default_enabled: [false, true]`, because the usage
+of the feature flag is undefined. These methods may change, as per the
+[related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/218667).
+
+The resulting YAML should be similar to:
+
+```yaml
+name: scoped_labels
+group: group::memory
+type: licensed
+# The `default_enabled:` is undefined
+# as `feature_available?` uses `default_enabled: true`
+# as `beta_feature_available?` uses `default_enabled: false`
+default_enabled: [false, true]
+```
+
### Feature groups
Feature groups must be defined statically in `lib/feature.rb` (in the
diff --git a/lib/feature/shared.rb b/lib/feature/shared.rb
index 53f027e3893..c06f699ef27 100644
--- a/lib/feature/shared.rb
+++ b/lib/feature/shared.rb
@@ -8,12 +8,14 @@ class Feature
module Shared
# optional: defines if a on-disk definition is required for this feature flag type
# rollout_issue: defines if `bin/feature-flag` asks for rollout issue
+ # default_enabled: defines a default state of a feature flag when created by `bin/feature-flag`
# example: usage being shown when exception is raised
TYPES = {
development: {
description: 'Short lived, used to enable unfinished code to be deployed',
optional: true,
rollout_issue: true,
+ default_enabled: false,
example: <<-EOS
Feature.enabled?(:my_feature_flag, project)
Feature.enabled?(:my_feature_flag, project, type: :development)
@@ -24,10 +26,21 @@ class Feature
description: "Long-lived feature flags that control operational aspects of GitLab's behavior",
optional: true,
rollout_issue: false,
+ default_enabled: false,
example: <<-EOS
Feature.enabled?(:my_ops_flag, type: ops)
push_frontend_feature_flag?(:my_ops_flag, project, type: :ops)
EOS
+ },
+ licensed: {
+ description: 'Permanent feature flags used to temporarily disable licensed features.',
+ optional: true,
+ rollout_issue: false,
+ default_enabled: true,
+ example: <<-EOS
+ project.feature_available?(:my_licensed_feature)
+ namespace.feature_available?(:my_licensed_feature)
+ EOS
}
}.freeze
diff --git a/lib/gitlab/background_migration/populate_vulnerability_historical_statistics.rb b/lib/gitlab/background_migration/populate_vulnerability_historical_statistics.rb
new file mode 100644
index 00000000000..a0c89cc4664
--- /dev/null
+++ b/lib/gitlab/background_migration/populate_vulnerability_historical_statistics.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This class creates/updates those project historical vulnerability statistics
+ # that haven't been created nor initialized. It should only be executed in EE.
+ class PopulateVulnerabilityHistoricalStatistics
+ def perform(project_ids)
+ end
+ end
+ end
+end
+
+Gitlab::BackgroundMigration::PopulateVulnerabilityHistoricalStatistics.prepend_if_ee('EE::Gitlab::BackgroundMigration::PopulateVulnerabilityHistoricalStatistics')
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0c756ec6644..cc0d4638536 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1833,9 +1833,6 @@ msgstr ""
msgid "AdminProjects|Delete Project %{projectName}?"
msgstr ""
-msgid "AdminProjects|Delete project"
-msgstr ""
-
msgid "AdminSettings|Apply integration settings to all Projects"
msgstr ""
@@ -2831,6 +2828,9 @@ msgstr ""
msgid "An error occurred while moving the issue."
msgstr ""
+msgid "An error occurred while moving the issue. Please try again."
+msgstr ""
+
msgid "An error occurred while parsing recent searches"
msgstr ""
@@ -6375,6 +6375,9 @@ msgstr ""
msgid "Commit…"
msgstr ""
+msgid "Community forum"
+msgstr ""
+
msgid "Company"
msgstr ""
@@ -7915,10 +7918,10 @@ msgstr ""
msgid "DastProfiles|Text file validation"
msgstr ""
-msgid "DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request."
+msgid "DastProfiles|The maximum number of minutes allowed for the spider to traverse the site."
msgstr ""
-msgid "DastProfiles|The maximum number of seconds allowed for the spider to traverse the site."
+msgid "DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request."
msgstr ""
msgid "DastProfiles|Validate"
@@ -16373,6 +16376,9 @@ msgstr ""
msgid "NetworkPolicies|Allow all outbound traffic from %{selector} to %{ruleSelector} on %{ports}"
msgstr ""
+msgid "NetworkPolicies|Are you sure you want to delete this policy? This action cannot be undone."
+msgstr ""
+
msgid "NetworkPolicies|Choose whether to enforce this policy."
msgstr ""
@@ -16382,6 +16388,12 @@ msgstr ""
msgid "NetworkPolicies|Define this policy's location, conditions and actions."
msgstr ""
+msgid "NetworkPolicies|Delete policy"
+msgstr ""
+
+msgid "NetworkPolicies|Delete policy: %{policy}"
+msgstr ""
+
msgid "NetworkPolicies|Deny all traffic"
msgstr ""
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb
index cb02d84081b..6b21d84cb13 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb
@@ -17,7 +17,7 @@ module QA
end
context 'when the snippet is public' do
- it 'can be shared with not signed-in users' do
+ it 'can be shared with not signed-in users', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1016' do
snippet
sharing_link = Page::Dashboard::Snippet::Show.perform do |snippet|
@@ -39,7 +39,7 @@ module QA
end
context 'when the snippet is changed to private' do
- it 'does not display Embed/Share dropdown' do
+ it 'does not display Embed/Share dropdown', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1015' do
snippet
Page::Dashboard::Snippet::Show.perform do |snippet|
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index 8d20db9f24a..717ab5f3add 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -1,5 +1,9 @@
+/* global ListIssue */
+/* global List */
+
import Vue from 'vue';
-import List from '~/boards/models/list';
+import '~/boards/models/list';
+import '~/boards/models/issue';
import boardsStore from '~/boards/stores/boards_store';
export const boardObj = {
@@ -94,11 +98,40 @@ export const mockMilestone = {
due_date: '2019-12-31',
};
+export const rawIssue = {
+ title: 'Testing',
+ id: 'gid://gitlab/Issue/1',
+ iid: 1,
+ confidential: false,
+ referencePath: 'gitlab-org/gitlab-test#1',
+ labels: {
+ nodes: [
+ {
+ id: 1,
+ title: 'test',
+ color: 'red',
+ description: 'testing',
+ },
+ ],
+ },
+ assignees: {
+ nodes: [
+ {
+ id: 1,
+ name: 'name',
+ username: 'username',
+ avatar_url: 'http://avatar_url',
+ },
+ ],
+ },
+};
+
export const mockIssue = {
title: 'Testing',
id: 1,
iid: 1,
confidential: false,
+ referencePath: 'gitlab-org/gitlab-test#1',
labels: [
{
id: 1,
@@ -117,11 +150,14 @@ export const mockIssue = {
],
};
+export const mockIssueWithModel = new ListIssue(mockIssue);
+
export const mockIssue2 = {
title: 'Planning',
id: 2,
iid: 2,
confidential: false,
+ referencePath: 'gitlab-org/gitlab-test#2',
labels: [
{
id: 1,
@@ -140,6 +176,8 @@ export const mockIssue2 = {
],
};
+export const mockIssue2WithModel = new ListIssue(mockIssue2);
+
export const BoardsMockData = {
GET: {
'/test/-/boards/1/lists/300/issues?id=300&page=1': {
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index 17792a8f597..4eb1a370a9f 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -1,5 +1,13 @@
import testAction from 'helpers/vuex_action_helper';
-import { mockListsWithModel, mockLists, mockIssue } from '../mock_data';
+import {
+ mockListsWithModel,
+ mockLists,
+ mockIssue,
+ mockIssue2,
+ mockIssueWithModel,
+ mockIssue2WithModel,
+ rawIssue,
+} from '../mock_data';
import actions, { gqlClient } from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types';
import { inactiveId, ListType } from '~/boards/constants';
@@ -229,7 +237,107 @@ describe('fetchIssuesForList', () => {
});
describe('moveIssue', () => {
- expectNotImplemented(actions.moveIssue);
+ const listIssues = {
+ 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
+ 'gid://gitlab/List/2': [],
+ };
+
+ const issues = {
+ '1': mockIssueWithModel,
+ '2': mockIssue2WithModel,
+ };
+
+ const state = {
+ endpoints: { fullPath: 'gitlab-org', boardId: '1' },
+ boardType: 'group',
+ disabled: false,
+ boardLists: mockListsWithModel,
+ issuesByListId: listIssues,
+ issues,
+ };
+
+ it('should commit MOVE_ISSUE mutation and MOVE_ISSUE_SUCCESS mutation when successful', done => {
+ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
+ data: {
+ issueMoveList: {
+ issue: rawIssue,
+ errors: [],
+ },
+ },
+ });
+
+ testAction(
+ actions.moveIssue,
+ {
+ issueId: mockIssue.id,
+ issueIid: mockIssue.iid,
+ issuePath: mockIssue.referencePath,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ },
+ state,
+ [
+ {
+ type: types.MOVE_ISSUE,
+ payload: {
+ originalIssue: mockIssueWithModel,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ },
+ },
+ {
+ type: types.MOVE_ISSUE_SUCCESS,
+ payload: { issue: rawIssue },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('should commit MOVE_ISSUE mutation and MOVE_ISSUE_FAILURE mutation when unsuccessful', done => {
+ jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
+ data: {
+ issueMoveList: {
+ issue: {},
+ errors: [{ foo: 'bar' }],
+ },
+ },
+ });
+
+ testAction(
+ actions.moveIssue,
+ {
+ issueId: mockIssue.id,
+ issueIid: mockIssue.iid,
+ issuePath: mockIssue.referencePath,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ },
+ state,
+ [
+ {
+ type: types.MOVE_ISSUE,
+ payload: {
+ originalIssue: mockIssueWithModel,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ },
+ },
+ {
+ type: types.MOVE_ISSUE_FAILURE,
+ payload: {
+ originalIssue: mockIssueWithModel,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ originalIndex: 0,
+ },
+ },
+ ],
+ [],
+ done,
+ );
+ });
});
describe('createNewIssue', () => {
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index 2927337f455..350eccfa82e 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -4,10 +4,13 @@ import defaultState from '~/boards/stores/state';
import {
listObj,
listObjDuplicate,
- mockIssue,
- mockIssue2,
mockListsWithModel,
mockLists,
+ rawIssue,
+ mockIssue,
+ mockIssue2,
+ mockIssueWithModel,
+ mockIssue2WithModel,
} from '../mock_data';
const expectNotImplemented = action => {
@@ -247,16 +250,86 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_ERROR);
});
- describe('REQUEST_MOVE_ISSUE', () => {
- expectNotImplemented(mutations.REQUEST_MOVE_ISSUE);
+ describe('MOVE_ISSUE', () => {
+ it('updates issuesByListId, moving issue between lists', () => {
+ const listIssues = {
+ 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
+ 'gid://gitlab/List/2': [],
+ };
+
+ const issues = {
+ '1': mockIssueWithModel,
+ '2': mockIssue2WithModel,
+ };
+
+ state = {
+ ...state,
+ issuesByListId: listIssues,
+ boardLists: mockListsWithModel,
+ issues,
+ };
+
+ mutations.MOVE_ISSUE(state, {
+ originalIssue: mockIssue2WithModel,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ });
+
+ const updatedListIssues = {
+ 'gid://gitlab/List/1': [mockIssue.id],
+ 'gid://gitlab/List/2': [mockIssue2.id],
+ };
+
+ expect(state.issuesByListId).toEqual(updatedListIssues);
+ });
});
- describe('RECEIVE_MOVE_ISSUE_SUCCESS', () => {
- expectNotImplemented(mutations.RECEIVE_MOVE_ISSUE_SUCCESS);
+ describe('MOVE_ISSUE_SUCCESS', () => {
+ it('updates issue in issues state', () => {
+ const issues = {
+ '1': { id: rawIssue.id },
+ };
+
+ state = {
+ ...state,
+ issues,
+ };
+
+ mutations.MOVE_ISSUE_SUCCESS(state, {
+ issue: rawIssue,
+ });
+
+ expect(state.issues).toEqual({ '1': { ...mockIssueWithModel, id: 1 } });
+ });
});
- describe('RECEIVE_MOVE_ISSUE_ERROR', () => {
- expectNotImplemented(mutations.RECEIVE_MOVE_ISSUE_ERROR);
+ describe('MOVE_ISSUE_FAILURE', () => {
+ it('updates issuesByListId, reverting moving issue between lists, and sets error message', () => {
+ const listIssues = {
+ 'gid://gitlab/List/1': [mockIssue.id],
+ 'gid://gitlab/List/2': [mockIssue2.id],
+ };
+
+ state = {
+ ...state,
+ issuesByListId: listIssues,
+ };
+
+ mutations.MOVE_ISSUE_FAILURE(state, {
+ originalIssue: mockIssue2,
+ fromListId: 'gid://gitlab/List/1',
+ toListId: 'gid://gitlab/List/2',
+ originalIndex: 1,
+ });
+
+ const updatedListIssues = {
+ 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
+ 'gid://gitlab/List/2': [],
+ };
+
+ expect(state.issuesByListId).toEqual(updatedListIssues);
+ expect(state.error).toEqual('An error occurred while moving the issue. Please try again.');
+ });
});
describe('REQUEST_UPDATE_ISSUE', () => {
diff --git a/spec/frontend/design_management/mock_data/apollo_mock.js b/spec/frontend/design_management/mock_data/apollo_mock.js
index 5e2df3877a5..1c7806c292f 100644
--- a/spec/frontend/design_management/mock_data/apollo_mock.js
+++ b/spec/frontend/design_management/mock_data/apollo_mock.js
@@ -13,6 +13,9 @@ export const designListQueryResponse = {
notesCount: 3,
image: 'image-1',
imageV432x230: 'image-1',
+ currentUserTodos: {
+ nodes: [],
+ },
},
{
id: '2',
@@ -21,6 +24,9 @@ export const designListQueryResponse = {
notesCount: 2,
image: 'image-2',
imageV432x230: 'image-2',
+ currentUserTodos: {
+ nodes: [],
+ },
},
{
id: '3',
@@ -29,6 +35,9 @@ export const designListQueryResponse = {
notesCount: 1,
image: 'image-3',
imageV432x230: 'image-3',
+ currentUserTodos: {
+ nodes: [],
+ },
},
],
},
@@ -60,6 +69,9 @@ export const reorderedDesigns = [
notesCount: 2,
image: 'image-2',
imageV432x230: 'image-2',
+ currentUserTodos: {
+ nodes: [],
+ },
},
{
id: '1',
@@ -68,6 +80,9 @@ export const reorderedDesigns = [
notesCount: 3,
image: 'image-1',
imageV432x230: 'image-1',
+ currentUserTodos: {
+ nodes: [],
+ },
},
{
id: '3',
@@ -76,6 +91,9 @@ export const reorderedDesigns = [
notesCount: 1,
image: 'image-3',
imageV432x230: 'image-3',
+ currentUserTodos: {
+ nodes: [],
+ },
},
];
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
index 9b7eb4324b3..c2091a681f2 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
@@ -38,9 +38,7 @@ describe('DropdownCreateLabelComponent', () => {
});
it('renders `Go back` button on component header', () => {
- const backButtonEl = vm.$el.querySelector(
- '.dropdown-title button.dropdown-title-button.dropdown-menu-back',
- );
+ const backButtonEl = vm.$el.querySelector('.dropdown-title .dropdown-menu-back');
expect(backButtonEl).not.toBe(null);
expect(backButtonEl.querySelector('[data-testid="arrow-left-icon"]')).not.toBe(null);
@@ -62,12 +60,9 @@ describe('DropdownCreateLabelComponent', () => {
});
it('renders `Close` button on component header', () => {
- const closeButtonEl = vm.$el.querySelector(
- '.dropdown-title button.dropdown-title-button.dropdown-menu-close',
- );
+ const closeButtonEl = vm.$el.querySelector('.dropdown-title .dropdown-menu-close');
expect(closeButtonEl).not.toBe(null);
- expect(closeButtonEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBe(null);
});
it('renders `Name new label` input element', () => {
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 498566c5d60..03e24524f9f 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -286,10 +286,10 @@ RSpec.describe NotificationService, :mailer do
describe 'Notes' do
context 'issue note' do
- let(:project) { create(:project, :private) }
- let(:issue) { create(:issue, project: project, assignees: [assignee]) }
- let(:mentioned_issue) { create(:issue, assignees: issue.assignees) }
- let(:author) { create(:user) }
+ let_it_be(:project) { create(:project, :private) }
+ let_it_be(:issue) { create(:issue, project: project, assignees: [assignee]) }
+ let_it_be(:mentioned_issue) { create(:issue, assignees: issue.assignees) }
+ let_it_be_with_reload(:author) { create(:user) }
let(:note) { create(:note_on_issue, author: author, noteable: issue, project_id: issue.project_id, note: '@mention referenced, @unsubscribed_mentioned and @outsider also') }
subject { notification.new_note(note) }
@@ -368,7 +368,7 @@ RSpec.describe NotificationService, :mailer do
end
describe '#new_note' do
- before do
+ before_all do
build_team(project)
project.add_maintainer(issue.author)
project.add_maintainer(assignee)
@@ -390,9 +390,12 @@ RSpec.describe NotificationService, :mailer do
end
context 'with users' do
- before do
+ before_all do
add_users(project)
add_user_subscriptions(issue)
+ end
+
+ before do
reset_delivered_emails!
end
@@ -569,17 +572,20 @@ RSpec.describe NotificationService, :mailer do
end
context 'issue note mention', :deliver_mails_inline do
- let(:issue) { create(:issue, project: project, assignees: [assignee]) }
- let(:mentioned_issue) { create(:issue, assignees: issue.assignees) }
- let(:author) { create(:user) }
+ let_it_be(:issue) { create(:issue, project: project, assignees: [assignee]) }
+ let_it_be(:mentioned_issue) { create(:issue, assignees: issue.assignees) }
+ let_it_be(:author) { create(:user) }
let(:note) { create(:note_on_issue, author: author, noteable: issue, project_id: issue.project_id, note: '@all mentioned') }
- before do
+ before_all do
build_team(project)
build_group(project)
add_users(project)
add_user_subscriptions(issue)
project.add_maintainer(author)
+ end
+
+ before do
reset_delivered_emails!
end
@@ -726,18 +732,21 @@ RSpec.describe NotificationService, :mailer do
end
context 'commit note', :deliver_mails_inline do
- let(:project) { create(:project, :public, :repository) }
- let(:note) { create(:note_on_commit, project: project) }
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:note) { create(:note_on_commit, project: project) }
- before do
+ before_all do
build_team(project)
build_group(project)
- reset_delivered_emails!
- allow(note.noteable).to receive(:author).and_return(@u_committer)
update_custom_notification(:new_note, @u_guest_custom, resource: project)
update_custom_notification(:new_note, @u_custom_global)
end
+ before do
+ reset_delivered_emails!
+ allow(note.noteable).to receive(:author).and_return(@u_committer)
+ end
+
describe '#new_note, #perform_enqueued_jobs' do
it do
notification.new_note(note)
@@ -785,12 +794,12 @@ RSpec.describe NotificationService, :mailer do
end
context "merge request diff note", :deliver_mails_inline do
- let(:project) { create(:project, :repository) }
- let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request, source_project: project, assignees: [user], author: create(:user)) }
- let(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:merge_request) { create(:merge_request, source_project: project, assignees: [user], author: create(:user)) }
+ let_it_be(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) }
- before do
+ before_all do
build_team(note.project)
project.add_maintainer(merge_request.author)
merge_request.assignees.each { |assignee| project.add_maintainer(assignee) }
@@ -2521,15 +2530,15 @@ RSpec.describe NotificationService, :mailer do
describe 'Pipelines', :deliver_mails_inline do
describe '#pipeline_finished' do
- let(:project) { create(:project, :public, :repository) }
- let(:u_member) { create(:user) }
- let(:u_watcher) { create_user_with_notification(:watch, 'watcher') }
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:u_member) { create(:user) }
+ let_it_be(:u_watcher) { create_user_with_notification(:watch, 'watcher') }
- let(:u_custom_notification_unset) do
+ let_it_be(:u_custom_notification_unset) do
create_user_with_notification(:custom, 'custom_unset')
end
- let(:u_custom_notification_enabled) do
+ let_it_be(:u_custom_notification_enabled) do
user = create_user_with_notification(:custom, 'custom_enabled')
update_custom_notification(:success_pipeline, user, resource: project)
update_custom_notification(:failed_pipeline, user, resource: project)
@@ -2537,7 +2546,7 @@ RSpec.describe NotificationService, :mailer do
user
end
- let(:u_custom_notification_disabled) do
+ let_it_be(:u_custom_notification_disabled) do
user = create_user_with_notification(:custom, 'custom_disabled')
update_custom_notification(:success_pipeline, user, resource: project, value: false)
update_custom_notification(:failed_pipeline, user, resource: project, value: false)
@@ -2556,13 +2565,15 @@ RSpec.describe NotificationService, :mailer do
before_sha: '00000000')
end
- before do
+ before_all do
project.add_maintainer(u_member)
project.add_maintainer(u_watcher)
project.add_maintainer(u_custom_notification_unset)
project.add_maintainer(u_custom_notification_enabled)
project.add_maintainer(u_custom_notification_disabled)
+ end
+ before do
reset_delivered_emails!
end
diff --git a/spec/workers/create_pipeline_worker_spec.rb b/spec/workers/create_pipeline_worker_spec.rb
index 6a3729fa28a..da85d700429 100644
--- a/spec/workers/create_pipeline_worker_spec.rb
+++ b/spec/workers/create_pipeline_worker_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe CreatePipelineWorker do
context 'when a project not found' do
it 'does not call the Service' do
expect(Ci::CreatePipelineService).not_to receive(:new)
- expect { worker.perform(99, create(:user).id, 'master', :web) }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { worker.perform(non_existing_record_id, create(:user).id, 'master', :web) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
@@ -18,7 +18,7 @@ RSpec.describe CreatePipelineWorker do
it 'does not call the Service' do
expect(Ci::CreatePipelineService).not_to receive(:new)
- expect { worker.perform(project.id, 99, project.default_branch, :web) }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { worker.perform(project.id, non_existing_record_id, project.default_branch, :web) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb
index 570b5d9c06e..7cba3487603 100644
--- a/spec/workers/new_issue_worker_spec.rb
+++ b/spec/workers/new_issue_worker_spec.rb
@@ -11,13 +11,13 @@ RSpec.describe NewIssueWorker do
expect(EventCreateService).not_to receive(:new)
expect(NotificationService).not_to receive(:new)
- worker.perform(99, create(:user).id)
+ worker.perform(non_existing_record_id, create(:user).id)
end
it 'logs an error' do
- expect(Gitlab::AppLogger).to receive(:error).with('NewIssueWorker: couldn\'t find Issue with ID=99, skipping job')
+ expect(Gitlab::AppLogger).to receive(:error).with("NewIssueWorker: couldn't find Issue with ID=#{non_existing_record_id}, skipping job")
- worker.perform(99, create(:user).id)
+ worker.perform(non_existing_record_id, create(:user).id)
end
end
@@ -26,15 +26,15 @@ RSpec.describe NewIssueWorker do
expect(EventCreateService).not_to receive(:new)
expect(NotificationService).not_to receive(:new)
- worker.perform(create(:issue).id, 99)
+ worker.perform(create(:issue).id, non_existing_record_id)
end
it 'logs an error' do
issue = create(:issue)
- expect(Gitlab::AppLogger).to receive(:error).with('NewIssueWorker: couldn\'t find User with ID=99, skipping job')
+ expect(Gitlab::AppLogger).to receive(:error).with("NewIssueWorker: couldn't find User with ID=#{non_existing_record_id}, skipping job")
- worker.perform(issue.id, 99)
+ worker.perform(issue.id, non_existing_record_id)
end
end
@@ -50,7 +50,7 @@ RSpec.describe NewIssueWorker do
it 'creates a notification for the mentioned user' do
expect(Notify).to receive(:new_issue_email).with(mentioned.id, issue.id, NotificationReason::MENTIONED)
- .and_return(double(deliver_later: true))
+ .and_return(double(deliver_later: true))
worker.perform(issue.id, user.id)
end
diff --git a/spec/workers/new_merge_request_worker_spec.rb b/spec/workers/new_merge_request_worker_spec.rb
index 3d78b9703fa..310fde4c7e1 100644
--- a/spec/workers/new_merge_request_worker_spec.rb
+++ b/spec/workers/new_merge_request_worker_spec.rb
@@ -11,15 +11,15 @@ RSpec.describe NewMergeRequestWorker do
expect(EventCreateService).not_to receive(:new)
expect(NotificationService).not_to receive(:new)
- worker.perform(99, create(:user).id)
+ worker.perform(non_existing_record_id, create(:user).id)
end
it 'logs an error' do
user = create(:user)
- expect(Gitlab::AppLogger).to receive(:error).with('NewMergeRequestWorker: couldn\'t find MergeRequest with ID=99, skipping job')
+ expect(Gitlab::AppLogger).to receive(:error).with("NewMergeRequestWorker: couldn't find MergeRequest with ID=#{non_existing_record_id}, skipping job")
- worker.perform(99, user.id)
+ worker.perform(non_existing_record_id, user.id)
end
end
@@ -28,15 +28,15 @@ RSpec.describe NewMergeRequestWorker do
expect(EventCreateService).not_to receive(:new)
expect(NotificationService).not_to receive(:new)
- worker.perform(create(:merge_request).id, 99)
+ worker.perform(create(:merge_request).id, non_existing_record_id)
end
it 'logs an error' do
merge_request = create(:merge_request)
- expect(Gitlab::AppLogger).to receive(:error).with('NewMergeRequestWorker: couldn\'t find User with ID=99, skipping job')
+ expect(Gitlab::AppLogger).to receive(:error).with("NewMergeRequestWorker: couldn't find User with ID=#{non_existing_record_id}, skipping job")
- worker.perform(merge_request.id, 99)
+ worker.perform(merge_request.id, non_existing_record_id)
end
end
@@ -54,8 +54,8 @@ RSpec.describe NewMergeRequestWorker do
it 'creates a notification for the mentioned user' do
expect(Notify).to receive(:new_merge_request_email)
- .with(mentioned.id, merge_request.id, NotificationReason::MENTIONED)
- .and_return(double(deliver_later: true))
+ .with(mentioned.id, merge_request.id, NotificationReason::MENTIONED)
+ .and_return(double(deliver_later: true))
worker.perform(merge_request.id, user.id)
end