summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-03-31 18:08:39 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-31 18:08:39 +0000
commite7fb61499317b3c044845817b991e13aea276955 (patch)
tree127aef09952fa595b8e5b886db5dd89d974d91da
parent85ea3dd4f4855e99d9a69c8e609095199597195a (diff)
downloadgitlab-ce-e7fb61499317b3c044845817b991e13aea276955.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/boards/components/board_card_inner.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue4
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue13
-rw-r--r--app/assets/javascripts/boards/components/issue_time_estimate.vue2
-rw-r--r--app/assets/javascripts/boards/components/item_count.vue2
-rw-r--r--app/assets/javascripts/boards/components/toggle_focus.vue1
-rw-r--r--app/assets/javascripts/boards/config_toggle.js25
-rw-r--r--app/assets/javascripts/boards/index.js3
-rw-r--r--app/assets/javascripts/boards/mount_filtered_search_issue_boards.js42
-rw-r--r--app/assets/javascripts/boards/mount_multiple_boards_switcher.js50
-rw-r--r--app/assets/javascripts/boards/new_board.js29
-rw-r--r--app/assets/javascripts/boards/toggle_epics_swimlanes.js1
-rw-r--r--app/assets/javascripts/boards/toggle_focus.js18
-rw-r--r--app/assets/javascripts/boards/toggle_labels.js1
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue17
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js3
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js4
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue10
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue12
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js2
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue54
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js12
-rw-r--r--app/assets/stylesheets/page_bundles/boards.scss2
-rw-r--r--app/controllers/groups/boards_controller.rb2
-rw-r--r--app/controllers/import/base_controller.rb2
-rw-r--r--app/controllers/projects/boards_controller.rb2
-rw-r--r--app/controllers/projects/pipeline_schedules_controller.rb4
-rw-r--r--app/helpers/boards_helper.rb14
-rw-r--r--app/helpers/packages_helper.rb2
-rw-r--r--app/models/ci/namespace_mirror.rb2
-rw-r--r--app/models/container_repository.rb2
-rw-r--r--app/models/project_import_state.rb19
-rw-r--r--app/models/user.rb46
-rw-r--r--app/services/incident_management/issuable_escalation_statuses/build_service.rb34
-rw-r--r--app/services/incident_management/issuable_escalation_statuses/create_service.rb19
-rw-r--r--app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb6
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml2
-rw-r--r--app/views/projects/registry/repositories/index.html.haml2
-rw-r--r--app/views/projects/settings/packages_and_registries/show.html.haml2
-rw-r--r--app/views/shared/boards/_switcher.html.haml12
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml357
-rw-r--r--config/feature_flags/development/deployment_approval_rules.yml (renamed from config/feature_flags/development/pipeline_schedules_with_tags.yml)12
-rw-r--r--db/post_migrate/20220323130000_add_temp_index_on_null_project_namespace_ids.rb15
-rw-r--r--db/schema_migrations/202203231300001
-rw-r--r--db/structure.sql2
-rw-r--r--doc/.vale/gitlab/SubstitutionWarning.yml1
-rw-r--r--doc/ci/runners/runners_scope.md8
-rw-r--r--doc/development/diffs.md4
-rw-r--r--doc/development/documentation/styleguide/word_list.md8
-rw-r--r--doc/user/group/subgroups/index.md13
-rw-r--r--lib/gitlab/ci/runner_upgrade_check.rb62
-rw-r--r--lib/gitlab/github_import/object_counter.rb2
-rw-r--r--locale/gitlab.pot8
-rw-r--r--spec/controllers/import/github_controller_spec.rb4
-rw-r--r--spec/factories/merge_requests.rb3
-rw-r--r--spec/features/boards/boards_spec.rb2
-rw-r--r--spec/features/boards/focus_mode_spec.rb2
-rw-r--r--spec/features/boards/multi_select_spec.rb6
-rw-r--r--spec/frontend/boards/components/boards_selector_spec.js6
-rw-r--r--spec/frontend/boards/components/issue_time_estimate_spec.js8
-rw-r--r--spec/frontend/boards/components/item_count_spec.js6
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js10
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js31
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js24
-rw-r--r--spec/frontend/packages_and_registries/shared/components/__snapshots__/cleanup_policy_enabled_alert_spec.js.snap19
-rw-r--r--spec/frontend/packages_and_registries/shared/components/cleanup_policy_enabled_alert_spec.js49
-rw-r--r--spec/helpers/packages_helper_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/runner_upgrade_check_spec.rb84
-rw-r--r--spec/lib/gitlab/github_import/object_counter_spec.rb10
-rw-r--r--spec/models/container_repository_spec.rb3
-rw-r--r--spec/models/project_import_state_spec.rb44
-rw-r--r--spec/services/incident_management/issuable_escalation_statuses/build_service_spec.rb20
-rw-r--r--spec/services/incident_management/issuable_escalation_statuses/create_service_spec.rb23
-rw-r--r--spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb7
-rw-r--r--spec/services/issues/update_service_spec.rb12
-rw-r--r--spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/incident_management/issuable_escalation_statuses/build_examples.rb20
-rw-r--r--spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_issue_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb2
83 files changed, 676 insertions, 709 deletions
diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue
index aee61a5b2a5..02b99e86fd3 100644
--- a/app/assets/javascripts/boards/components/board_card_inner.vue
+++ b/app/assets/javascripts/boards/components/board_card_inner.vue
@@ -240,7 +240,7 @@ export default {
class="board-card-footer gl-display-flex gl-justify-content-space-between gl-align-items-flex-end"
>
<div
- class="gl-display-flex align-items-start flex-wrap-reverse board-card-number-container gl-overflow-hidden js-board-card-number-container"
+ class="gl-display-flex align-items-start flex-wrap-reverse board-card-number-container gl-overflow-hidden"
>
<gl-loading-icon v-if="item.isLoading" size="md" class="mt-3" />
<span
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index aba1f42ead5..a874c9e070a 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -289,7 +289,7 @@ export default {
<p v-if="isDeleteForm" data-testid="delete-confirmation-message">
{{ $options.i18n.deleteConfirmationMessage }}
</p>
- <form v-else class="js-board-config-modal" data-testid="board-form-wrapper" @submit.prevent>
+ <form v-else data-testid="board-form-wrapper" @submit.prevent>
<div v-if="!readonly" class="gl-mb-5" data-testid="board-form">
<label class="gl-font-weight-bold gl-font-lg" for="board-new-name">
{{ $options.i18n.titleFieldLabel }}
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 1024be61359..858cd368b12 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -287,7 +287,7 @@ export default {
:data-board-type="list.listType"
:class="{ 'bg-danger-100': boardItemsSizeExceedsMax }"
draggable=".board-card"
- class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-2 js-board-list"
+ class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-2"
data-testid="tree-root-wrapper"
@start="handleDragOnStart"
@end="handleDragOnEnd"
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index 46b28d20da9..f02feab264c 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -262,7 +262,7 @@ export default {
'gl-py-2': list.collapsed && isSwimlanesHeader,
'gl-flex-direction-column': list.collapsed,
}"
- class="board-title gl-m-0 gl-display-flex gl-align-items-center gl-font-base gl-px-3 js-board-handle"
+ class="board-title gl-m-0 gl-display-flex gl-align-items-center gl-font-base gl-px-3"
>
<gl-button
v-gl-tooltip.hover
@@ -443,7 +443,7 @@ export default {
ref="settingsBtn"
v-gl-tooltip.hover
:aria-label="$options.i18n.listSettings"
- class="no-drag js-board-settings-button"
+ class="no-drag"
:title="$options.i18n.listSettings"
icon="settings"
@click="openSidebarSettings"
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 8333856e996..2951eda1112 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -239,11 +239,12 @@ export default {
</script>
<template>
- <div class="boards-switcher js-boards-selector gl-mr-3">
- <span class="boards-selector-wrapper js-boards-selector-wrapper">
+ <div class="boards-switcher gl-mr-3" data-testid="boards-selector">
+ <span class="boards-selector-wrapper">
<gl-dropdown
+ data-testid="boards-dropdown"
data-qa-selector="boards_dropdown"
- toggle-class="dropdown-menu-toggle js-dropdown-toggle"
+ toggle-class="dropdown-menu-toggle"
menu-class="flex-column dropdown-extended-height"
:loading="isBoardLoading"
:text="board.name"
@@ -276,8 +277,8 @@ export default {
<gl-dropdown-item
v-for="recentBoard in recentBoards"
:key="`recent-${recentBoard.id}`"
- class="js-dropdown-item"
:href="`${boardBaseUrl}/${recentBoard.id}`"
+ data-testid="dropdown-item"
>
{{ recentBoard.name }}
</gl-dropdown-item>
@@ -292,8 +293,8 @@ export default {
<gl-dropdown-item
v-for="otherBoard in filteredBoards"
:key="otherBoard.id"
- class="js-dropdown-item"
:href="`${boardBaseUrl}/${otherBoard.id}`"
+ data-testid="dropdown-item"
>
{{ otherBoard.name }}
</gl-dropdown-item>
@@ -331,7 +332,7 @@ export default {
<gl-dropdown-item
v-if="showDelete"
v-gl-modal-directive="'board-config-modal'"
- class="text-danger js-delete-board"
+ class="text-danger"
@click.prevent="showPage('delete')"
>
{{ s__('IssueBoards|Delete board') }}
diff --git a/app/assets/javascripts/boards/components/issue_time_estimate.vue b/app/assets/javascripts/boards/components/issue_time_estimate.vue
index 1ab7deebfaf..9312db06efe 100644
--- a/app/assets/javascripts/boards/components/issue_time_estimate.vue
+++ b/app/assets/javascripts/boards/components/issue_time_estimate.vue
@@ -43,7 +43,7 @@ export default {
<gl-tooltip
:target="() => $refs.issueTimeEstimate"
placement="bottom"
- class="js-issue-time-estimate"
+ data-testid="issue-time-estimate"
>
<span class="gl-font-weight-bold gl-display-block">{{ $options.i18n.timeEstimate }}</span>
{{ title }}
diff --git a/app/assets/javascripts/boards/components/item_count.vue b/app/assets/javascripts/boards/components/item_count.vue
index 9b1ff254766..a11c23e5625 100644
--- a/app/assets/javascripts/boards/components/item_count.vue
+++ b/app/assets/javascripts/boards/components/item_count.vue
@@ -29,7 +29,7 @@ export default {
<span :class="{ 'text-danger': issuesExceedMax }" data-testid="board-items-count">
{{ itemsSize }}
</span>
- <span v-if="isMaxLimitSet" class="js-max-issue-size">
+ <span v-if="isMaxLimitSet" class="max-issue-size">
{{ maxIssueCount }}
</span>
</div>
diff --git a/app/assets/javascripts/boards/components/toggle_focus.vue b/app/assets/javascripts/boards/components/toggle_focus.vue
index 0169c9c72ed..71612e0742f 100644
--- a/app/assets/javascripts/boards/components/toggle_focus.vue
+++ b/app/assets/javascripts/boards/components/toggle_focus.vue
@@ -38,7 +38,6 @@ export default {
v-gl-tooltip
category="tertiary"
:icon="isFullscreen ? 'minimize' : 'maximize'"
- class="js-focus-mode-btn"
data-qa-selector="focus_mode_button"
:title="$options.i18n.toggleFocusMode"
:aria-label="$options.i18n.toggleFocusMode"
diff --git a/app/assets/javascripts/boards/config_toggle.js b/app/assets/javascripts/boards/config_toggle.js
deleted file mode 100644
index 1e54c2511b8..00000000000
--- a/app/assets/javascripts/boards/config_toggle.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import Vue from 'vue';
-import { parseBoolean } from '~/lib/utils/common_utils';
-import ConfigToggle from './components/config_toggle.vue';
-
-export default () => {
- const el = document.querySelector('.js-board-config');
-
- if (!el) {
- return;
- }
-
- // eslint-disable-next-line no-new
- new Vue({
- el,
- name: 'ConfigToggleRoot',
- render(h) {
- return h(ConfigToggle, {
- props: {
- canAdminList: parseBoolean(el.dataset.canAdminList),
- hasScope: parseBoolean(el.dataset.hasScope),
- },
- });
- },
- });
-};
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 2abc283c5b4..77c5994b5a1 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -13,7 +13,6 @@ import {
} from '~/lib/utils/common_utils';
import { queryToObject } from '~/lib/utils/url_utility';
import { fullBoardId } from './boards_util';
-import initNewBoard from './new_board';
import { gqlClient } from './graphql';
Vue.use(VueApollo);
@@ -112,6 +111,4 @@ export default () => {
});
mountBoardApp($boardApp);
-
- initNewBoard();
};
diff --git a/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js b/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js
deleted file mode 100644
index bb659eb075a..00000000000
--- a/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import Vue from 'vue';
-import IssueBoardFilteredSearch from 'ee_else_ce/boards/components/issue_board_filtered_search.vue';
-import store from '~/boards/stores';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { queryToObject } from '~/lib/utils/url_utility';
-
-export default (
- apolloProvider,
- isSignedIn,
- releasesFetchPath,
- epicFeatureAvailable,
- iterationFeatureAvailable,
-) => {
- const el = document.getElementById('js-issue-board-filtered-search');
- const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
-
- const initialFilterParams = {
- ...convertObjectPropsToCamelCase(rawFilterParams, {}),
- };
-
- if (!el) {
- return null;
- }
-
- return new Vue({
- el,
- name: 'BoardFilteredSearchRoot',
- provide: {
- initialFilterParams,
- isSignedIn,
- releasesFetchPath,
- epicFeatureAvailable,
- iterationFeatureAvailable,
- },
- store, // TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/324094
- apolloProvider,
- render: (createElement) =>
- createElement(IssueBoardFilteredSearch, {
- props: { fullPath: store.state?.fullPath || '', boardType: store.state?.boardType || '' },
- }),
- });
-};
diff --git a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
deleted file mode 100644
index 0bc9cfbd867..00000000000
--- a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import BoardsSelector from 'ee_else_ce/boards/components/boards_selector.vue';
-import store from '~/boards/stores';
-import createDefaultClient from '~/lib/graphql';
-import { parseBoolean } from '~/lib/utils/common_utils';
-
-Vue.use(VueApollo);
-
-const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(),
-});
-
-export default (params = {}) => {
- const boardsSwitcherElement = document.getElementById('js-multiple-boards-switcher');
- const { dataset } = boardsSwitcherElement;
- return new Vue({
- el: boardsSwitcherElement,
- name: 'BoardsSelectorRoot',
- components: {
- BoardsSelector,
- },
- apolloProvider,
- store,
- provide: {
- fullPath: params.fullPath,
- rootPath: params.rootPath,
- allowScopedLabels: params.allowScopedLabels,
- labelsManagePath: params.labelsManagePath,
- allowLabelCreate: parseBoolean(dataset.canAdminBoard),
- },
- data() {
- const boardsSelectorProps = {
- ...dataset,
- hasMissingBoards: parseBoolean(dataset.hasMissingBoards),
- canAdminBoard: parseBoolean(dataset.canAdminBoard),
- multipleIssueBoardsAvailable: parseBoolean(dataset.multipleIssueBoardsAvailable),
- scopedIssueBoardFeatureEnabled: parseBoolean(dataset.scopedIssueBoardFeatureEnabled),
- weights: JSON.parse(dataset.weights),
- };
-
- return { boardsSelectorProps };
- },
- render(createElement) {
- return createElement(BoardsSelector, {
- props: this.boardsSelectorProps,
- });
- },
- });
-};
diff --git a/app/assets/javascripts/boards/new_board.js b/app/assets/javascripts/boards/new_board.js
deleted file mode 100644
index 34f2fea79a9..00000000000
--- a/app/assets/javascripts/boards/new_board.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import Vue from 'vue';
-import { parseBoolean } from '~/lib/utils/common_utils';
-import { getExperimentVariant } from '~/experimentation/utils';
-import { CANDIDATE_VARIANT } from '~/experimentation/constants';
-import NewBoardButton from './components/new_board_button.vue';
-
-export default () => {
- if (getExperimentVariant('prominent_create_board_btn') !== CANDIDATE_VARIANT) {
- return;
- }
-
- const el = document.querySelector('.js-new-board');
-
- if (!el) {
- return;
- }
-
- // eslint-disable-next-line no-new
- new Vue({
- el,
- provide: {
- multipleIssueBoardsAvailable: parseBoolean(el.dataset.multipleIssueBoardsAvailable),
- canAdminBoard: parseBoolean(el.dataset.canAdminBoard),
- },
- render(h) {
- return h(NewBoardButton);
- },
- });
-};
diff --git a/app/assets/javascripts/boards/toggle_epics_swimlanes.js b/app/assets/javascripts/boards/toggle_epics_swimlanes.js
deleted file mode 100644
index 2d1ec238274..00000000000
--- a/app/assets/javascripts/boards/toggle_epics_swimlanes.js
+++ /dev/null
@@ -1 +0,0 @@
-export default () => {};
diff --git a/app/assets/javascripts/boards/toggle_focus.js b/app/assets/javascripts/boards/toggle_focus.js
deleted file mode 100644
index 8f057e192dd..00000000000
--- a/app/assets/javascripts/boards/toggle_focus.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import Vue from 'vue';
-import ToggleFocus from './components/toggle_focus.vue';
-
-export default () => {
- const issueBoardsContentSelector = '.content-wrapper > .js-focus-mode-board';
-
- return new Vue({
- el: '#js-toggle-focus-btn',
- name: 'ToggleFocusRoot',
- render(h) {
- return h(ToggleFocus, {
- props: {
- issueBoardsContentSelector,
- },
- });
- },
- });
-};
diff --git a/app/assets/javascripts/boards/toggle_labels.js b/app/assets/javascripts/boards/toggle_labels.js
deleted file mode 100644
index 2d1ec238274..00000000000
--- a/app/assets/javascripts/boards/toggle_labels.js
+++ /dev/null
@@ -1 +0,0 @@
-export default () => {};
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue
index 6d2ff9ea7b6..154e176dc6e 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue
@@ -1,4 +1,5 @@
<script>
+import { GlLink } from '@gitlab/ui';
import { approximateDuration, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility';
import { n__, sprintf } from '~/locale';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
@@ -9,6 +10,7 @@ import {
LIST_INTRO_TEXT,
EXPIRATION_POLICY_WILL_RUN_IN,
EXPIRATION_POLICY_DISABLED_TEXT,
+ SET_UP_CLEANUP,
} from '../../constants/index';
export default {
@@ -16,6 +18,7 @@ export default {
components: {
TitleArea,
MetadataItem,
+ GlLink,
},
props: {
expirationPolicy: {
@@ -43,6 +46,16 @@ export default {
required: false,
default: false,
},
+ cleanupPoliciesSettingsPath: {
+ type: String,
+ default: '',
+ required: false,
+ },
+ showCleanupPolicyLink: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
loader: {
repeat: 10,
@@ -51,6 +64,7 @@ export default {
},
i18n: {
CONTAINER_REGISTRY_TITLE,
+ SET_UP_CLEANUP,
},
computed: {
imagesCountText() {
@@ -105,6 +119,9 @@ export default {
:text="expirationPolicyText"
size="xl"
/>
+ <gl-link v-if="showCleanupPolicyLink" class="gl-ml-2" :href="cleanupPoliciesSettingsPath">{{
+ $options.i18n.SET_UP_CLEANUP
+ }}</gl-link>
</template>
</title-area>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js
index 40f9b09a982..e584da23edb 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js
@@ -4,7 +4,7 @@ export const EXPIRATION_POLICY_WILL_RUN_IN = s__(
'ContainerRegistry|Expiration policy will run in %{time}',
);
export const EXPIRATION_POLICY_DISABLED_TEXT = s__(
- 'ContainerRegistry|Expiration policy is disabled',
+ 'ContainerRegistry|Expiration policy is disabled.',
);
export const DELETE_ALERT_TITLE = s__('ContainerRegistry|Some tags were not deleted');
export const DELETE_ALERT_LINK_TEXT = s__(
@@ -13,3 +13,4 @@ export const DELETE_ALERT_LINK_TEXT = s__(
export const CLEANUP_TIMED_OUT_ERROR_MESSAGE = s__(
'ContainerRegistry|Cleanup timed out before it could delete all tags',
);
+export const SET_UP_CLEANUP = s__('ContainerRegistry|Set up cleanup');
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js
index ca5bd8d6964..a558550c91f 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js
@@ -35,7 +35,7 @@ export default () => {
expirationPolicy,
isGroupPage,
isAdmin,
- showCleanupPolicyOnAlert,
+ showCleanupPolicyLink,
showUnfinishedTagCleanupCallout,
connectionError,
invalidPathError,
@@ -68,7 +68,7 @@ export default () => {
expirationPolicy: expirationPolicy ? JSON.parse(expirationPolicy) : undefined,
isGroupPage: parseBoolean(isGroupPage),
isAdmin: parseBoolean(isAdmin),
- showCleanupPolicyOnAlert: parseBoolean(showCleanupPolicyOnAlert),
+ showCleanupPolicyLink: parseBoolean(showCleanupPolicyLink),
showUnfinishedTagCleanupCallout: parseBoolean(showUnfinishedTagCleanupCallout),
connectionError: parseBoolean(connectionError),
invalidPathError: parseBoolean(invalidPathError),
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
index 5f9e614bebb..d1cab406984 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
@@ -11,7 +11,6 @@ import {
import { get } from 'lodash';
import getContainerRepositoriesQuery from 'shared_queries/container_registry/get_container_repositories.query.graphql';
import createFlash from '~/flash';
-import CleanupPolicyEnabledAlert from '~/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue';
import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants';
import Tracking from '~/tracking';
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
@@ -60,7 +59,6 @@ export default {
GlSkeletonLoader,
RegistryHeader,
DeleteImage,
- CleanupPolicyEnabledAlert,
PersistedSearch,
},
directives: {
@@ -273,12 +271,6 @@ export default {
</gl-sprintf>
</gl-alert>
- <cleanup-policy-enabled-alert
- v-if="config.showCleanupPolicyOnAlert"
- :project-path="config.projectPath"
- :cleanup-policies-settings-path="config.cleanupPoliciesSettingsPath"
- />
-
<gl-empty-state
v-if="showConnectionError"
:title="$options.i18n.CONNECTION_ERROR_TITLE"
@@ -304,6 +296,8 @@ export default {
:expiration-policy="config.expirationPolicy"
:help-page-path="config.helpPagePath"
:hide-expiration-policy-data="config.isGroupPage"
+ :cleanup-policies-settings-path="config.cleanupPoliciesSettingsPath"
+ :show-cleanup-policy-link="config.showCleanupPolicyLink"
>
<template #commands>
<cli-commands
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
index 7be3bba7cae..854c88b2ad3 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
@@ -9,7 +9,6 @@ import {
UNAVAILABLE_ADMIN_FEATURE_TEXT,
} from '~/packages_and_registries/settings/project/constants';
import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
-import CleanupPolicyEnabledAlert from '~/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue';
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
import SettingsForm from './settings_form.vue';
@@ -18,19 +17,11 @@ export default {
components: {
SettingsBlock,
SettingsForm,
- CleanupPolicyEnabledAlert,
GlAlert,
GlSprintf,
GlLink,
},
- inject: [
- 'projectPath',
- 'isAdmin',
- 'adminSettingsPath',
- 'enableHistoricEntries',
- 'helpPagePath',
- 'showCleanupPolicyOnAlert',
- ],
+ inject: ['projectPath', 'isAdmin', 'adminSettingsPath', 'enableHistoricEntries', 'helpPagePath'],
i18n: {
UNAVAILABLE_FEATURE_TITLE,
UNAVAILABLE_FEATURE_INTRO_TEXT,
@@ -87,7 +78,6 @@ export default {
<template>
<section data-testid="registry-settings-app">
- <cleanup-policy-enabled-alert v-if="showCleanupPolicyOnAlert" :project-path="projectPath" />
<settings-block :collapsible="false">
<template #title> {{ __('Clean up image tags') }}</template>
<template #description>
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js b/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js
index 2a3e2c28fa6..17c33073668 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js
+++ b/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js
@@ -20,7 +20,6 @@ export default () => {
adminSettingsPath,
tagsRegexHelpPagePath,
helpPagePath,
- showCleanupPolicyOnAlert,
} = el.dataset;
return new Vue({
el,
@@ -35,7 +34,6 @@ export default () => {
adminSettingsPath,
tagsRegexHelpPagePath,
helpPagePath,
- showCleanupPolicyOnAlert: parseBoolean(showCleanupPolicyOnAlert),
},
render(createElement) {
return createElement('registry-settings-app', {});
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue b/app/assets/javascripts/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue
deleted file mode 100644
index d51c62e0623..00000000000
--- a/app/assets/javascripts/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-<script>
-import { GlSprintf, GlAlert, GlLink } from '@gitlab/ui';
-import { s__ } from '~/locale';
-import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
-
-export default {
- components: {
- GlAlert,
- GlLink,
- GlSprintf,
- LocalStorageSync,
- },
- props: {
- projectPath: {
- type: String,
- required: true,
- },
- cleanupPoliciesSettingsPath: {
- type: String,
- required: false,
- default: '',
- },
- },
- data() {
- return {
- dismissed: false,
- };
- },
- computed: {
- storageKey() {
- return `cleanup_policy_enabled_for_project_${this.projectPath}`;
- },
- },
- i18n: {
- message: s__(
- 'ContainerRegistry|Cleanup policies are now available for this project. %{linkStart}Click here to get started.%{linkEnd}',
- ),
- },
-};
-</script>
-
-<template>
- <local-storage-sync v-model="dismissed" :storage-key="storageKey">
- <gl-alert v-if="!dismissed" class="gl-mt-2" dismissible @dismiss="dismissed = true">
- <gl-sprintf :message="$options.i18n.message">
- <template #link="{ content }">
- <gl-link v-if="cleanupPoliciesSettingsPath" :href="cleanupPoliciesSettingsPath">{{
- content
- }}</gl-link>
- </template>
- </gl-sprintf>
- </gl-alert>
- </local-storage-sync>
-</template>
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
index 88ff05b20a0..5dae812bbcb 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
@@ -33,13 +33,7 @@ function initIntervalPatternInput() {
}
function getEnabledRefTypes() {
- const refTypes = [REF_TYPE_BRANCHES];
-
- if (gon.features.pipelineSchedulesWithTags) {
- refTypes.push(REF_TYPE_TAGS);
- }
-
- return refTypes;
+ return [REF_TYPE_BRANCHES, REF_TYPE_TAGS];
}
function initTargetRefDropdown() {
@@ -61,9 +55,7 @@ function initTargetRefDropdown() {
value: $refField.value,
useSymbolicRefNames: true,
translations: {
- dropdownHeader: gon.features.pipelineSchedulesWithTags
- ? __('Select target branch or tag')
- : __('Select target branch'),
+ dropdownHeader: __('Select target branch or tag'),
},
},
class: 'gl-w-full',
diff --git a/app/assets/stylesheets/page_bundles/boards.scss b/app/assets/stylesheets/page_bundles/boards.scss
index f91ca489bdf..eecd4954e39 100644
--- a/app/assets/stylesheets/page_bundles/boards.scss
+++ b/app/assets/stylesheets/page_bundles/boards.scss
@@ -198,7 +198,7 @@
border-bottom: 1px solid var(--gray-100, $gray-100);
height: 3rem;
- .js-max-issue-size::before {
+ .max-issue-size::before {
content: '/';
}
}
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index 641b3adb12b..e223e2a914d 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -43,8 +43,6 @@ class Groups::BoardsController < Groups::ApplicationController
def assign_endpoint_vars
@boards_endpoint = group_boards_path(group)
- @namespace_path = group.to_param
- @labels_endpoint = group_labels_path(group)
end
def authorize_read_board!
diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb
index 7ad3a2ee358..936ecc171d7 100644
--- a/app/controllers/import/base_controller.rb
+++ b/app/controllers/import/base_controller.rb
@@ -70,7 +70,7 @@ class Import::BaseController < ApplicationController
end
def already_added_projects
- @already_added_projects ||= filtered(find_already_added_projects(provider_name))
+ @already_added_projects ||= find_already_added_projects(provider_name)
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index c44a0830e2e..b82e60e9ac7 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -44,8 +44,6 @@ class Projects::BoardsController < Projects::ApplicationController
def assign_endpoint_vars
@boards_endpoint = project_boards_path(project)
@bulk_issues_path = bulk_update_project_issues_path(project)
- @namespace_path = project.namespace.full_path
- @labels_endpoint = project_labels_path(project)
end
def authorize_read_board!
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index 271c31b6429..ac94cc001dd 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -10,10 +10,6 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
before_action :authorize_update_pipeline_schedule!, except: [:index, :new, :create, :play]
before_action :authorize_admin_pipeline_schedule!, only: [:destroy]
- before_action do
- push_frontend_feature_flag(:pipeline_schedules_with_tags, @project, default_enabled: :yaml)
- end
-
feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index 7b4d87a77e4..f849f36bf84 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -127,20 +127,6 @@ module BoardsHelper
can?(current_user, :admin_issue, current_board_parent)
end
- def board_list_data
- include_descendant_groups = @group&.present?
-
- {
- toggle: "dropdown",
- list_labels_path: labels_filter_path_with_defaults(only_group_labels: true, include_ancestor_groups: true),
- labels: labels_filter_path_with_defaults(only_group_labels: true, include_descendant_groups: include_descendant_groups),
- labels_endpoint: @labels_endpoint,
- namespace_path: @namespace_path,
- project_path: @project&.path,
- group_path: @group&.path
- }
- end
-
def serializer
CurrentBoardSerializer.new
end
diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb
index 01075862618..20d40626449 100644
--- a/app/helpers/packages_helper.rb
+++ b/app/helpers/packages_helper.rb
@@ -46,7 +46,7 @@ module PackagesHelper
::Gitlab::Tracking.event(category, event_name.to_s, **args)
end
- def show_cleanup_policy_on_alert(project)
+ def show_cleanup_policy_link(project)
Gitlab.com? &&
Gitlab.config.registry.enabled &&
project.feature_available?(:container_registry, current_user) &&
diff --git a/app/models/ci/namespace_mirror.rb b/app/models/ci/namespace_mirror.rb
index b4b9574942e..d5cbbb96134 100644
--- a/app/models/ci/namespace_mirror.rb
+++ b/app/models/ci/namespace_mirror.rb
@@ -11,8 +11,6 @@ module Ci
end
scope :contains_any_of_namespaces, -> (ids) do
- return none if ids.empty?
-
where('traversal_ids && ARRAY[?]::int[]', ids)
end
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 03ac5c8e5df..28b4b0dcca1 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -174,7 +174,7 @@ class ContainerRepository < ApplicationRecord
end
end
- before_transition %i[importing import_aborted] => :import_done do |container_repository|
+ before_transition any => :import_done do |container_repository|
container_repository.migration_import_done_at = Time.zone.now
end
diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb
index 42914b0d668..fabbd5b49cb 100644
--- a/app/models/project_import_state.rb
+++ b/app/models/project_import_state.rb
@@ -6,6 +6,8 @@ class ProjectImportState < ApplicationRecord
self.table_name = "project_mirror_data"
+ after_commit :expire_etag_cache
+
belongs_to :project, inverse_of: :import_state
validates :project, presence: true
@@ -76,6 +78,23 @@ class ProjectImportState < ApplicationRecord
end
end
+ def expire_etag_cache
+ if realtime_changes_path
+ Gitlab::EtagCaching::Store.new.tap do |store|
+ store.touch(realtime_changes_path)
+ rescue Gitlab::EtagCaching::Store::InvalidKeyError
+ # no-op: not every realtime changes endpoint is using etag caching
+ end
+ end
+ end
+
+ def realtime_changes_path
+ Gitlab::Routing.url_helpers.polymorphic_path([:realtime_changes_import, project.import_type.to_sym], format: :json)
+ rescue NoMethodError
+ # polymorphic_path throws NoMethodError when no such path exists
+ nil
+ end
+
def relation_hard_failures(limit:)
project.import_failures.hard_failures_by_correlation_id(correlation_id).limit(limit)
end
diff --git a/app/models/user.rb b/app/models/user.rb
index c683f5dd252..281e52b90b0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2220,27 +2220,43 @@ class User < ApplicationRecord
end
def ci_owned_project_runners_from_project_members
- Ci::RunnerProject
- .select('ci_runners.*')
- .joins(:runner)
- .where(project: project_members.where('access_level >= ?', Gitlab::Access::MAINTAINER).pluck(:source_id))
+ project_ids = project_members.where('access_level >= ?', Gitlab::Access::MAINTAINER).pluck(:source_id)
+
+ Ci::Runner
+ .joins(:runner_projects)
+ .where(runner_projects: { project: project_ids })
end
def ci_owned_project_runners_from_group_members
- Ci::RunnerProject
- .select('ci_runners.*')
- .joins(:runner)
- .joins('JOIN ci_project_mirrors ON ci_project_mirrors.project_id = ci_runner_projects.project_id')
- .joins('JOIN ci_namespace_mirrors ON ci_namespace_mirrors.namespace_id = ci_project_mirrors.namespace_id')
- .merge(ci_namespace_mirrors_for_group_members(Gitlab::Access::MAINTAINER))
+ cte_namespace_ids = Gitlab::SQL::CTE.new(
+ :cte_namespace_ids,
+ ci_namespace_mirrors_for_group_members(Gitlab::Access::MAINTAINER).select(:namespace_id)
+ )
+
+ cte_project_ids = Gitlab::SQL::CTE.new(
+ :cte_project_ids,
+ Ci::ProjectMirror
+ .select(:project_id)
+ .where('ci_project_mirrors.namespace_id IN (SELECT namespace_id FROM cte_namespace_ids)')
+ )
+
+ Ci::Runner
+ .with(cte_namespace_ids.to_arel)
+ .with(cte_project_ids.to_arel)
+ .joins(:runner_projects)
+ .where('ci_runner_projects.project_id IN (SELECT project_id FROM cte_project_ids)')
end
def ci_owned_group_runners
- Ci::RunnerNamespace
- .select('ci_runners.*')
- .joins(:runner)
- .joins('JOIN ci_namespace_mirrors ON ci_namespace_mirrors.namespace_id = ci_runner_namespaces.namespace_id')
- .merge(ci_namespace_mirrors_for_group_members(Gitlab::Access::OWNER))
+ cte_namespace_ids = Gitlab::SQL::CTE.new(
+ :cte_namespace_ids,
+ ci_namespace_mirrors_for_group_members(Gitlab::Access::OWNER).select(:namespace_id)
+ )
+
+ Ci::Runner
+ .with(cte_namespace_ids.to_arel)
+ .joins(:runner_namespaces)
+ .where('ci_runner_namespaces.namespace_id IN (SELECT namespace_id FROM cte_namespace_ids)')
end
def ci_namespace_mirrors_for_group_members(level)
diff --git a/app/services/incident_management/issuable_escalation_statuses/build_service.rb b/app/services/incident_management/issuable_escalation_statuses/build_service.rb
new file mode 100644
index 00000000000..9ebcf72a0c9
--- /dev/null
+++ b/app/services/incident_management/issuable_escalation_statuses/build_service.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module IncidentManagement
+ module IssuableEscalationStatuses
+ class BuildService < ::BaseProjectService
+ def initialize(issue)
+ @issue = issue
+ @alert = issue.alert_management_alert
+
+ super(project: issue.project)
+ end
+
+ def execute
+ return issue.escalation_status if issue.escalation_status
+
+ issue.build_incident_management_issuable_escalation_status(alert_params)
+ end
+
+ private
+
+ attr_reader :issue, :alert
+
+ def alert_params
+ return {} unless alert
+
+ {
+ status_event: alert.status_event_for(alert.status_name)
+ }
+ end
+ end
+ end
+end
+
+IncidentManagement::IssuableEscalationStatuses::BuildService.prepend_mod
diff --git a/app/services/incident_management/issuable_escalation_statuses/create_service.rb b/app/services/incident_management/issuable_escalation_statuses/create_service.rb
index e28debf0fa3..9b22fb97e0d 100644
--- a/app/services/incident_management/issuable_escalation_statuses/create_service.rb
+++ b/app/services/incident_management/issuable_escalation_statuses/create_service.rb
@@ -2,14 +2,15 @@
module IncidentManagement
module IssuableEscalationStatuses
- class CreateService < BaseService
+ class CreateService < ::BaseProjectService
def initialize(issue)
@issue = issue
- @alert = issue.alert_management_alert
+
+ super(project: issue.project)
end
def execute
- escalation_status = ::IncidentManagement::IssuableEscalationStatus.new(issue: issue, **alert_params)
+ escalation_status = BuildService.new(issue).execute
if escalation_status.save
ServiceResponse.success(payload: { escalation_status: escalation_status })
@@ -20,17 +21,7 @@ module IncidentManagement
private
- attr_reader :issue, :alert
-
- def alert_params
- return {} unless alert
-
- {
- status_event: alert.status_event_for(alert.status_name)
- }
- end
+ attr_reader :issue
end
end
end
-
-IncidentManagement::IssuableEscalationStatuses::CreateService.prepend_mod
diff --git a/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb b/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb
index 8f591b375ee..1d0504a6e80 100644
--- a/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb
+++ b/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb
@@ -31,9 +31,7 @@ module IncidentManagement
attr_reader :issuable, :param_errors
def available?
- issuable.supports_escalation? &&
- user_has_permissions? &&
- escalation_status.present?
+ issuable.supports_escalation? && user_has_permissions?
end
def user_has_permissions?
@@ -42,7 +40,7 @@ module IncidentManagement
def escalation_status
strong_memoize(:escalation_status) do
- issuable.escalation_status
+ issuable.escalation_status || BuildService.new(issuable).execute
end
end
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index 0818c3d5cff..e00d8e2c050 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -15,7 +15,7 @@
= f.text_field :cron_timezone, value: @schedule.cron_timezone, id: 'schedule_cron_timezone', class: 'hidden', name: 'schedule[cron_timezone]', required: true
.form-group.row
.col-md-9
- = f.label :ref, Feature.enabled?(:pipeline_schedules_with_tags, default_enabled: :yaml) ? _('Target branch or tag') : _('Target branch'), class: 'label-bold'
+ = f.label :ref, _('Target branch or tag'), class: 'label-bold'
%div{ data: { testid: 'schedule-target-ref' } }
.js-target-ref-dropdown{ data: { project_id: @project.id, default_branch: @project.default_branch } }
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml
index aab5e9fca98..51f0b6319a1 100644
--- a/app/views/projects/registry/repositories/index.html.haml
+++ b/app/views/projects/registry/repositories/index.html.haml
@@ -19,7 +19,7 @@
"project_path": @project.full_path,
"gid_prefix": container_repository_gid_prefix,
"is_admin": current_user&.admin.to_s,
- "show_cleanup_policy_on_alert": show_cleanup_policy_on_alert(@project).to_s,
+ "show_cleanup_policy_link": show_cleanup_policy_link(@project).to_s,
"cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project),
connection_error: (!!@connection_error).to_s,
invalid_path_error: (!!@invalid_path_error).to_s,
diff --git a/app/views/projects/settings/packages_and_registries/show.html.haml b/app/views/projects/settings/packages_and_registries/show.html.haml
index 658b2f2e65c..378bb0f9306 100644
--- a/app/views/projects/settings/packages_and_registries/show.html.haml
+++ b/app/views/projects/settings/packages_and_registries/show.html.haml
@@ -11,5 +11,5 @@
admin_settings_path: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'),
enable_historic_entries: container_expiration_policies_historic_entry_enabled?.to_s,
help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'cleanup-policy'),
- show_cleanup_policy_on_alert: show_cleanup_policy_on_alert(@project).to_s,
+ show_cleanup_policy_link: show_cleanup_policy_link(@project).to_s,
tags_regex_help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'regex-pattern-examples') } }
diff --git a/app/views/shared/boards/_switcher.html.haml b/app/views/shared/boards/_switcher.html.haml
deleted file mode 100644
index c667b3a4626..00000000000
--- a/app/views/shared/boards/_switcher.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-- parent = board.resource_parent
-- milestone_filter_opts = { format: :json }
-- milestone_filter_opts = milestone_filter_opts.merge(only_group_milestones: true) if board.group_board?
-- weights = Gitlab.ee? ? ([Issue::WEIGHT_ANY] + Issue.weight_options) : []
-
-#js-multiple-boards-switcher.inline.boards-switcher{ data: { milestone_path: milestones_filter_path(milestone_filter_opts),
- board_base_url: board_base_url,
- has_missing_boards: (!multiple_boards_available? && current_board_parent.boards.size > 1).to_s,
- can_admin_board: can?(current_user, :admin_issue_board, parent).to_s,
- multiple_issue_boards_available: parent.multiple_issue_boards_available?.to_s,
- scoped_issue_board_feature_enabled: Gitlab.ee? && parent.feature_available?(:scoped_issue_board) ? 'true' : 'false',
- weights: weights.to_json } }
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 37a79a50fb1..7fdf8ea7796 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -1,22 +1,12 @@
- type = local_assigns.fetch(:type)
-- board = local_assigns.fetch(:board, nil)
- show_sorting_dropdown = local_assigns.fetch(:show_sorting_dropdown, true)
- disable_target_branch = local_assigns.fetch(:disable_target_branch, false)
- placeholder = local_assigns[:placeholder] || _('Search or filter results...')
- block_css_class = type != :productivity_analytics ? 'row-content-block second-block' : ''
-- is_epic_board = board&.to_type == "EpicBoard"
-
-- if is_epic_board
- - user_can_admin_list = can?(current_user, :admin_epic_board_list, board.resource_parent)
-- elsif board
- - user_can_admin_list = can?(current_user, :admin_issue_board_list, board.resource_parent)
.issues-filters
.issues-details-filters.filtered-search-block.d-flex.flex-column.flex-lg-row{ class: block_css_class }
.d-flex.flex-column.flex-md-row.flex-grow-1.mb-lg-0.mb-md-2.mb-sm-0.w-100
- - if type == :boards
- = render "shared/boards/switcher", board: board
- .js-new-board{ data: { multiple_issue_boards_available: parent.multiple_issue_boards_available?.to_s, can_admin_board: can?(current_user, :admin_issue_board, parent).to_s, } }
= form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form w-100' do
- if params[:search].present?
= hidden_field_tag :search, params[:search]
@@ -25,201 +15,188 @@
- checkbox_id = 'check-all-issues'
%label.gl-sr-only{ for: checkbox_id }= _('Select all')
= check_box_tag checkbox_id, nil, false, class: "check-all-issues left"
- - if is_epic_board
- #js-board-filtered-search{ data: { full_path: @group&.full_path } }
- - elsif board
- #js-issue-board-filtered-search
- - else
- .issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
- .filtered-search-box
- - if type != :boards
- - text = tag.span(sprite_icon('history'), class: "d-md-none") + tag.span(_('Recent searches'), class: "d-none d-md-inline")
- = dropdown_tag(text,
- options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
- toggle_class: "gl-button btn btn-default filtered-search-history-dropdown-toggle-button",
- dropdown_class: "filtered-search-history-dropdown",
- content_class: "filtered-search-history-dropdown-content" }) do
- .js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
- .filtered-search-box-input-container.droplab-dropdown
- .scroll-container
- %ul.tokens-container.list-unstyled
- %li.input-token
- %input.form-control.filtered-search{ search_filter_input_options(type, placeholder) }
- #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- -# Encapsulate static class name `{{icon}}` inside #{} to bypass
- -# haml lint's ClassAttributeWithStaticValue
- %svg
- %use{ 'xlink:href': "#{'{{icon}}'}" }
- %span.js-filter-hint
- {{formattedKey}}
- #js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu
- %ul.filter-dropdown{ data: { dropdown: true, dynamic: true } }
- %li.filter-dropdown-item{ data: { value: "{{ title }}" } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- {{ title }}
- %span.btn-helptext
- {{ help }}
- #js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
+ .issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
+ .filtered-search-box
+ - if type != :boards
+ - text = tag.span(sprite_icon('history'), class: "d-md-none") + tag.span(_('Recent searches'), class: "d-none d-md-inline")
+ = dropdown_tag(text,
+ options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
+ toggle_class: "gl-button btn btn-default filtered-search-history-dropdown-toggle-button",
+ dropdown_class: "filtered-search-history-dropdown",
+ content_class: "filtered-search-history-dropdown-content" }) do
+ .js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
+ .filtered-search-box-input-container.droplab-dropdown
+ .scroll-container
+ %ul.tokens-container.list-unstyled
+ %li.input-token
+ %input.form-control.filtered-search{ search_filter_input_options(type, placeholder) }
+ #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ -# Encapsulate static class name `{{icon}}` inside #{} to bypass
+ -# haml lint's ClassAttributeWithStaticValue
+ %svg
+ %use{ 'xlink:href': "#{'{{icon}}'}" }
+ %span.js-filter-hint
+ {{formattedKey}}
+ #js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dropdown: true, dynamic: true } }
+ %li.filter-dropdown-item{ data: { value: "{{ title }}" } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ {{ title }}
+ %span.btn-helptext
+ {{ help }}
+ #js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
+ - if current_user
+ %ul{ data: { dropdown: true } }
+ = render 'shared/issuable/user_dropdown_item',
+ user: current_user
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ = render 'shared/issuable/user_dropdown_item',
+ user: User.new(username: '{{username}}', name: '{{name}}'),
+ avatar: { lazy: true, url: '{{avatar_url}}' }
+ #js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
- if current_user
- %ul{ data: { dropdown: true } }
- = render 'shared/issuable/user_dropdown_item',
- user: current_user
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
- user: User.new(username: '{{username}}', name: '{{name}}'),
- avatar: { lazy: true, url: '{{avatar_url}}' }
- #js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.divider.droplab-item-ignore
- - if current_user
- = render 'shared/issuable/user_dropdown_item',
- user: current_user
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ user: current_user
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ = render 'shared/issuable/user_dropdown_item',
+ user: User.new(username: '{{username}}', name: '{{name}}'),
+ avatar: { lazy: true, url: '{{avatar_url}}' }
+ #js-dropdown-reviewer.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
+ - if current_user
= render 'shared/issuable/user_dropdown_item',
- user: User.new(username: '{{username}}', name: '{{name}}'),
- avatar: { lazy: true, url: '{{avatar_url}}' }
- #js-dropdown-reviewer.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.divider.droplab-item-ignore
- - if current_user
+ user: current_user
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ = render 'shared/issuable/user_dropdown_item',
+ user: User.new(username: '{{username}}', name: '{{name}}'),
+ avatar: { lazy: true, url: '{{avatar_url}}' }
+ - if Feature.enabled?(:mr_attention_requests, default_enabled: :yaml)
+ #js-dropdown-attention-requested.filtered-search-input-dropdown-menu.dropdown-menu
+ - if current_user
+ %ul{ data: { dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
user: current_user
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
user: User.new(username: '{{username}}', name: '{{name}}'),
avatar: { lazy: true, url: '{{avatar_url}}' }
- - if Feature.enabled?(:mr_attention_requests, default_enabled: :yaml)
- #js-dropdown-attention-requested.filtered-search-input-dropdown-menu.dropdown-menu
- - if current_user
- %ul{ data: { dropdown: true } }
- = render 'shared/issuable/user_dropdown_item',
- user: current_user
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- = render 'shared/issuable/user_dropdown_item',
- user: User.new(username: '{{username}}', name: '{{name}}'),
- avatar: { lazy: true, url: '{{avatar_url}}' }
- = render_if_exists 'shared/issuable/approver_dropdown'
- = render_if_exists 'shared/issuable/approved_by_dropdown'
- #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.filter-dropdown-item{ data: { value: 'Upcoming' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('Upcoming')
- %li.filter-dropdown-item{ data: { value: 'Started' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('Started')
- %li.divider.droplab-item-ignore
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.gl-button.btn.btn-link.js-data-value{ type: 'button' }
+ = render_if_exists 'shared/issuable/approver_dropdown'
+ = render_if_exists 'shared/issuable/approved_by_dropdown'
+ #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.filter-dropdown-item{ data: { value: 'Upcoming' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('Upcoming')
+ %li.filter-dropdown-item{ data: { value: 'Started' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('Started')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.gl-button.btn.btn-link.js-data-value{ type: 'button' }
+ {{title}}
+ = render_if_exists 'shared/issuable/filter_iteration', type: type
+ #js-dropdown-release.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.gl-button.btn.btn-link.js-data-value{ type: 'button' }
+ {{title}}
+ #js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ %span.dropdown-label-box{ style: 'background: {{color}}' }
+ %span.label-title.js-data-value
{{title}}
- = render_if_exists 'shared/issuable/filter_iteration', type: type
- #js-dropdown-release.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.divider.droplab-item-ignore
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.gl-button.btn.btn-link.js-data-value{ type: 'button' }
- {{title}}
- #js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.divider.droplab-item-ignore
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.gl-button.btn.btn-link{ type: 'button' }
- %span.dropdown-label-box{ style: 'background: {{color}}' }
- %span.label-title.js-data-value
- {{title}}
- #js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'None' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('None')
- %li.filter-dropdown-item{ data: { value: 'Any' } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('Any')
- %li.divider.droplab-item-ignore
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.gl-button.btn.btn-link{ type: 'button' }
- %gl-emoji
- %span.js-data-value.gl-ml-3
- {{name}}
- #js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
- %ul.filter-dropdown{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('Yes')
- %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('No')
- #js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
- %ul.filter-dropdown{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('Yes')
- %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
- %button.gl-button.btn.btn-link{ type: 'button' }
- = _('No')
- - unless disable_target_branch
- #js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.gl-button.btn.btn-link.js-data-value.monospace
- {{title}}
- #js-dropdown-environment.filtered-search-input-dropdown-menu.dropdown-menu
+ #js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'None' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'Any' } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('Any')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ %gl-emoji
+ %span.js-data-value.gl-ml-3
+ {{name}}
+ #js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('Yes')
+ %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('No')
+ #js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('Yes')
+ %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
+ %button.gl-button.btn.btn-link{ type: 'button' }
+ = _('No')
+ - unless disable_target_branch
+ #js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
- %button.gl-button.btn.btn-link.js-data-value{ type: 'button' }
+ %button.gl-button.btn.btn-link.js-data-value.monospace
{{title}}
+ #js-dropdown-environment.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.gl-button.btn.btn-link.js-data-value{ type: 'button' }
+ {{title}}
- = render_if_exists 'shared/issuable/filter_weight', type: type
+ = render_if_exists 'shared/issuable/filter_weight', type: type
- = render_if_exists 'shared/issuable/filter_epic', type: type
+ = render_if_exists 'shared/issuable/filter_epic', type: type
- %button.clear-search.hidden{ type: 'button' }
- = sprite_icon('close', size: 16, css_class: 'clear-search-icon')
+ %button.clear-search.hidden{ type: 'button' }
+ = sprite_icon('close', size: 16, css_class: 'clear-search-icon')
.filter-dropdown-container.gl-display-flex.gl-flex-direction-column.gl-md-flex-direction-row.gl-align-items-flex-start
- - if type == :boards
- #js-board-labels-toggle
- - if current_user
- #js-board-epics-swimlanes-toggle
- .js-board-config{ data: { can_admin_list: user_can_admin_list.to_s, has_scope: board.scoped?.to_s } }
- - if user_can_admin_list
- .js-create-column-trigger{ data: board_list_data }
- #js-toggle-focus-btn
- - elsif type != :productivity_analytics && show_sorting_dropdown
+ - if type != :productivity_analytics && show_sorting_dropdown
= render 'shared/issuable/sort_dropdown'
diff --git a/config/feature_flags/development/pipeline_schedules_with_tags.yml b/config/feature_flags/development/deployment_approval_rules.yml
index 9eb7b60d300..31b235053c3 100644
--- a/config/feature_flags/development/pipeline_schedules_with_tags.yml
+++ b/config/feature_flags/development/deployment_approval_rules.yml
@@ -1,8 +1,8 @@
---
-name: pipeline_schedules_with_tags
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81476
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354421
-milestone: '14.9'
+name: deployment_approval_rules
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83495
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354726
+milestone: '14.10'
type: development
-group: group::pipeline execution
-default_enabled: true
+group: group::release
+default_enabled: false
diff --git a/db/post_migrate/20220323130000_add_temp_index_on_null_project_namespace_ids.rb b/db/post_migrate/20220323130000_add_temp_index_on_null_project_namespace_ids.rb
new file mode 100644
index 00000000000..e73993065b0
--- /dev/null
+++ b/db/post_migrate/20220323130000_add_temp_index_on_null_project_namespace_ids.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddTempIndexOnNullProjectNamespaceIds < Gitlab::Database::Migration[1.0]
+ TMP_INDEX_FOR_NULL_PROJECT_NAMESPACE_ID = 'tmp_index_for_null_project_namespace_id'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :projects, :id, name: TMP_INDEX_FOR_NULL_PROJECT_NAMESPACE_ID, where: 'project_namespace_id IS NULL'
+ end
+
+ def down
+ remove_concurrent_index_by_name :projects, name: TMP_INDEX_FOR_NULL_PROJECT_NAMESPACE_ID
+ end
+end
diff --git a/db/schema_migrations/20220323130000 b/db/schema_migrations/20220323130000
new file mode 100644
index 00000000000..a370ab82750
--- /dev/null
+++ b/db/schema_migrations/20220323130000
@@ -0,0 +1 @@
+6152ff1a3647e1ac26e8f462215a0dec7d96c5552373f4ad99424c7c74e80577 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index fbae8724a83..5689a7b4f60 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -29634,6 +29634,8 @@ CREATE INDEX tmp_index_for_namespace_id_migration_on_group_members ON members US
CREATE INDEX tmp_index_for_namespace_id_migration_on_routes ON routes USING btree (id) WHERE ((namespace_id IS NULL) AND ((source_type)::text = 'Namespace'::text));
+CREATE INDEX tmp_index_for_null_project_namespace_id ON projects USING btree (id) WHERE (project_namespace_id IS NULL);
+
CREATE INDEX tmp_index_issues_on_issue_type_and_id ON issues USING btree (issue_type, id);
CREATE INDEX tmp_index_members_on_state ON members USING btree (state) WHERE (state = 2);
diff --git a/doc/.vale/gitlab/SubstitutionWarning.yml b/doc/.vale/gitlab/SubstitutionWarning.yml
index 84d1bab3cc6..7b20887f53f 100644
--- a/doc/.vale/gitlab/SubstitutionWarning.yml
+++ b/doc/.vale/gitlab/SubstitutionWarning.yml
@@ -11,6 +11,7 @@ link: https://about.gitlab.com/handbook/communication/#top-misused-terms
level: warning
ignorecase: true
swap:
+ air(?:-| )?gapped: offline environment
click: select
code base: codebase
config: configuration
diff --git a/doc/ci/runners/runners_scope.md b/doc/ci/runners/runners_scope.md
index aa7e268e800..fb5395076b0 100644
--- a/doc/ci/runners/runners_scope.md
+++ b/doc/ci/runners/runners_scope.md
@@ -143,6 +143,8 @@ Group runners process jobs by using a first in, first out ([FIFO](https://en.wik
### Create a group runner
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/19819) in GitLab 14.10, path changed from **Settings > CI/CD > Runners**.
+
You can create a group runner for your self-managed GitLab instance or for GitLab.com.
You must have the Owner role for the group.
@@ -150,7 +152,7 @@ To create a group runner:
1. [Install GitLab Runner](https://docs.gitlab.com/runner/install/).
1. Go to the group you want to make the runner work for.
-1. Go to **Settings > CI/CD** and expand the **Runners** section.
+1. On the left sidebar, select **CI/CD > Runners**.
1. Note the URL and token.
1. [Register the runner](https://docs.gitlab.com/runner/register/).
@@ -163,7 +165,7 @@ You can do this for your self-managed GitLab instance or for GitLab.com.
You must have the Owner role for the group.
1. Go to the group where you want to view the runners.
-1. Go to **Settings > CI/CD** and expand the **Runners** section.
+1. On the left sidebar, select **CI/CD > Runners**.
1. The following fields are displayed.
| Attribute | Description |
@@ -186,7 +188,7 @@ You can pause or remove a group runner for your self-managed GitLab instance or
You must have the Owner role for the group.
1. Go to the group you want to remove or pause the runner for.
-1. Go to **Settings > CI/CD** and expand the **Runners** section.
+1. On the left sidebar, select **CI/CD > Runners**.
1. Click **Pause** or **Remove runner**.
- If you pause a group runner that is used by multiple projects, the runner pauses for all projects.
- From the group view, you cannot remove a runner that is assigned to more than one project.
diff --git a/doc/development/diffs.md b/doc/development/diffs.md
index d61de740f15..5f03ba93a4d 100644
--- a/doc/development/diffs.md
+++ b/doc/development/diffs.md
@@ -1,6 +1,6 @@
---
-stage: none
-group: unassigned
+stage: Create
+group: Code Review
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index dcb9a5f7362..65f6a0a328b 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -97,6 +97,12 @@ The token generated when you create an agent for Kubernetes. Use **agent access
- secret token
- authentication token
+## air gap, air-gapped
+
+Use **offline environment** to describe installations that have physical barriers or security policies that prevent or limit internet access. Do not use **air gap**, **air gapped**, or **air-gapped**. For example:
+
+- The firewall policies in an offline environment prevent the computer from accessing the internet.
+
## allow, enable
Try to avoid **allow** and **enable**, unless you are talking about security-related features.
@@ -1023,7 +1029,7 @@ Do not use **yet** when talking about the product or its features. The documenta
Sometimes you might need to use **yet** when writing a task. If you use
**yet**, ensure the surrounding phrases are written
-in present tense, active voice.
+in present tense, active voice.
[View guidance about how to write about future features](index.md#promising-features-in-future-versions).
([Vale](../testing.md#vale) rule: [`CurrentStatus.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/CurrentStatus.yml))
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 76097e4f43a..2cddbaa9723 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -49,9 +49,16 @@ graph TD
## Create a subgroup
-Users with the at least the Maintainer role on a group can create subgroups immediately below the group, unless
-[configured otherwise](#change-who-can-create-subgroups). These users can create subgroups even if group creation is
-[disabled by an Administrator](../../admin_area/index.md#prevent-a-user-from-creating-groups) in the user's settings.
+Prerequisites:
+
+- You must either:
+ - Have at least the Maintainer role for a group to create subgroups for it.
+ - Have the [role determined by a setting](#change-who-can-create-subgroups). These users can create
+ subgroups even if group creation is
+ [disabled by an Administrator](../../admin_area/index.md#prevent-a-user-from-creating-groups) in the user's settings.
+
+NOTE:
+You cannot host a GitLab Pages subgroup website with a top-level domain name. For example, `subgroupname.example.io`.
To create a subgroup:
diff --git a/lib/gitlab/ci/runner_upgrade_check.rb b/lib/gitlab/ci/runner_upgrade_check.rb
new file mode 100644
index 00000000000..baf041fc358
--- /dev/null
+++ b/lib/gitlab/ci/runner_upgrade_check.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class RunnerUpgradeCheck
+ include Singleton
+
+ def initialize
+ reset!
+ end
+
+ def check_runner_upgrade_status(runner_version)
+ return :unknown unless runner_version
+
+ releases = RunnerReleases.instance.releases
+ parsed_runner_version = runner_version.is_a?(::Gitlab::VersionInfo) ? runner_version : ::Gitlab::VersionInfo.parse(runner_version)
+
+ raise ArgumentError, "'#{runner_version}' is not a valid version" unless parsed_runner_version.valid?
+
+ available_releases = releases.reject { |release| release > @gitlab_version }
+
+ return :recommended if available_releases.any? { |available_release| patch_update?(available_release, parsed_runner_version) }
+ return :recommended if outside_backport_window?(parsed_runner_version, releases)
+ return :available if available_releases.any? { |available_release| available_release > parsed_runner_version }
+
+ :not_available
+ end
+
+ def reset!
+ @gitlab_version = ::Gitlab::VersionInfo.parse(::Gitlab::VERSION)
+ end
+
+ public_class_method :instance
+
+ private
+
+ def patch_update?(available_release, runner_version)
+ # https://docs.gitlab.com/ee/policy/maintenance.html#patch-releases
+ available_release.major == runner_version.major &&
+ available_release.minor == runner_version.minor &&
+ available_release.patch > runner_version.patch
+ end
+
+ def outside_backport_window?(runner_version, releases)
+ return false if runner_version >= releases.last # return early if runner version is too new
+
+ latest_minor_releases = releases.map { |r| version_without_patch(r) }.uniq { |v| v.to_s }
+ latest_version_position = latest_minor_releases.count - 1
+ runner_version_position = latest_minor_releases.index(version_without_patch(runner_version))
+
+ return true if runner_version_position.nil? # consider outside if version is too old
+
+ # https://docs.gitlab.com/ee/policy/maintenance.html#backporting-to-older-releases
+ latest_version_position - runner_version_position > 2
+ end
+
+ def version_without_patch(version)
+ ::Gitlab::VersionInfo.new(version.major, version.minor, 0)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/object_counter.rb b/lib/gitlab/github_import/object_counter.rb
index 7ce88280209..8873db24118 100644
--- a/lib/gitlab/github_import/object_counter.rb
+++ b/lib/gitlab/github_import/object_counter.rb
@@ -24,6 +24,8 @@ module Gitlab
increment_project_counter(project, object_type, operation, integer)
increment_global_counter(object_type, operation, integer)
+
+ project.import_state&.expire_etag_cache
end
def summary(project)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index dd6dab41ca8..0d9c0c70d1e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -9678,9 +9678,6 @@ msgstr ""
msgid "ContainerRegistry|Cleanup pending"
msgstr ""
-msgid "ContainerRegistry|Cleanup policies are now available for this project. %{linkStart}Click here to get started.%{linkEnd}"
-msgstr ""
-
msgid "ContainerRegistry|Cleanup policy for tags is disabled"
msgstr ""
@@ -9738,7 +9735,7 @@ msgstr ""
msgid "ContainerRegistry|Enable expiration policy"
msgstr ""
-msgid "ContainerRegistry|Expiration policy is disabled"
+msgid "ContainerRegistry|Expiration policy is disabled."
msgstr ""
msgid "ContainerRegistry|Expiration policy will run in %{time}"
@@ -9833,6 +9830,9 @@ msgstr ""
msgid "ContainerRegistry|Run cleanup:"
msgstr ""
+msgid "ContainerRegistry|Set up cleanup"
+msgstr ""
+
msgid "ContainerRegistry|Some tags were not deleted"
msgstr ""
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index c3a4f49daac..fe03039ddcc 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -258,7 +258,9 @@ RSpec.describe Import::GithubController do
context 'when user input contains colons and spaces' do
before do
- allow(controller).to receive(:client_repos).and_return([])
+ allow_next_instance_of(Gitlab::GithubImport::Client) do |client|
+ allow(client).to receive(:search_repos_by_name).and_return(items: [])
+ end
end
it 'sanitizes user input' do
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 26804b38db8..e897a5e022a 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -65,11 +65,12 @@ FactoryBot.define do
transient do
merged_by { author }
+ merged_at { nil }
end
after(:build) do |merge_request, evaluator|
metrics = merge_request.build_metrics
- metrics.merged_at = 1.week.from_now
+ metrics.merged_at = evaluator.merged_at || 1.week.from_now
metrics.merged_by = evaluator.merged_by
metrics.pipeline = create(:ci_empty_pipeline)
end
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 5dd627f3b76..bf976168bbe 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -282,7 +282,7 @@ RSpec.describe 'Project issue boards', :js do
it 'shows issue count on the list' do
page.within(find(".board:nth-child(2)")) do
expect(page.find('[data-testid="board-items-count"]')).to have_text(total_planning_issues)
- expect(page).not_to have_selector('.js-max-issue-size')
+ expect(page).not_to have_selector('.max-issue-size')
end
end
end
diff --git a/spec/features/boards/focus_mode_spec.rb b/spec/features/boards/focus_mode_spec.rb
index 2bd1e625236..453a8d8870b 100644
--- a/spec/features/boards/focus_mode_spec.rb
+++ b/spec/features/boards/focus_mode_spec.rb
@@ -12,6 +12,6 @@ RSpec.describe 'Issue Boards focus mode', :js do
end
it 'shows focus mode button to anonymous users' do
- expect(page).to have_selector('.js-focus-mode-btn')
+ expect(page).to have_button _('Toggle focus mode')
end
end
diff --git a/spec/features/boards/multi_select_spec.rb b/spec/features/boards/multi_select_spec.rb
index 9148fb23214..cad303a14e5 100644
--- a/spec/features/boards/multi_select_spec.rb
+++ b/spec/features/boards/multi_select_spec.rb
@@ -72,7 +72,7 @@ RSpec.describe 'Multi Select Issue', :js do
wait_for_requests
- page.within(all('.js-board-list')[2]) do
+ page.within(all('.board-list')[2]) do
expect(find('.board-card:nth-child(1)')).to have_content(issue1.title)
expect(find('.board-card:nth-child(2)')).to have_content(issue2.title)
end
@@ -87,7 +87,7 @@ RSpec.describe 'Multi Select Issue', :js do
wait_for_requests
- page.within(all('.js-board-list')[2]) do
+ page.within(all('.board-list')[2]) do
expect(find('.board-card:nth-child(1)')).to have_content(issue1.title)
expect(find('.board-card:nth-child(2)')).to have_content(issue2.title)
expect(find('.board-card:nth-child(3)')).to have_content(issue3.title)
@@ -102,7 +102,7 @@ RSpec.describe 'Multi Select Issue', :js do
wait_for_requests
- page.within(all('.js-board-list')[1]) do
+ page.within(all('.board-list')[1]) do
expect(find('.board-card:nth-child(1)')).to have_content(issue1.title)
expect(find('.board-card:nth-child(2)')).to have_content(issue2.title)
expect(find('.board-card:nth-child(3)')).to have_content(issue5.title)
diff --git a/spec/frontend/boards/components/boards_selector_spec.js b/spec/frontend/boards/components/boards_selector_spec.js
index 717216a9882..f60d04af4fc 100644
--- a/spec/frontend/boards/components/boards_selector_spec.js
+++ b/spec/frontend/boards/components/boards_selector_spec.js
@@ -1,5 +1,4 @@
import { GlDropdown, GlLoadingIcon, GlDropdownSectionHeader } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
@@ -14,6 +13,7 @@ import groupRecentBoardsQuery from '~/boards/graphql/group_recent_boards.query.g
import projectRecentBoardsQuery from '~/boards/graphql/project_recent_boards.query.graphql';
import defaultStore from '~/boards/stores';
import createMockApollo from 'helpers/mock_apollo_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import {
mockGroupBoardResponse,
mockProjectBoardResponse,
@@ -60,7 +60,7 @@ describe('BoardsSelector', () => {
searchBoxInput.trigger('input');
};
- const getDropdownItems = () => wrapper.findAll('.js-dropdown-item');
+ const getDropdownItems = () => wrapper.findAllByTestId('dropdown-item');
const getDropdownHeaders = () => wrapper.findAllComponents(GlDropdownSectionHeader);
const getLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findDropdown = () => wrapper.findComponent(GlDropdown);
@@ -100,7 +100,7 @@ describe('BoardsSelector', () => {
[groupRecentBoardsQuery, groupRecentBoardsQueryHandlerSuccess],
]);
- wrapper = mount(BoardsSelector, {
+ wrapper = mountExtended(BoardsSelector, {
store,
apolloProvider: fakeApollo,
propsData: {
diff --git a/spec/frontend/boards/components/issue_time_estimate_spec.js b/spec/frontend/boards/components/issue_time_estimate_spec.js
index 635964b6b4a..948a7a20f7f 100644
--- a/spec/frontend/boards/components/issue_time_estimate_spec.js
+++ b/spec/frontend/boards/components/issue_time_estimate_spec.js
@@ -5,6 +5,8 @@ import IssueTimeEstimate from '~/boards/components/issue_time_estimate.vue';
describe('Issue Time Estimate component', () => {
let wrapper;
+ const findIssueTimeEstimate = () => wrapper.find('[data-testid="issue-time-estimate"]');
+
afterEach(() => {
wrapper.destroy();
});
@@ -26,7 +28,7 @@ describe('Issue Time Estimate component', () => {
});
it('renders expanded time estimate in tooltip', () => {
- expect(wrapper.find('.js-issue-time-estimate').text()).toContain('2 weeks 3 days 1 minute');
+ expect(findIssueTimeEstimate().text()).toContain('2 weeks 3 days 1 minute');
});
it('prevents tooltip xss', async () => {
@@ -42,7 +44,7 @@ describe('Issue Time Estimate component', () => {
expect(alertSpy).not.toHaveBeenCalled();
expect(wrapper.find('time').text().trim()).toEqual('0m');
- expect(wrapper.find('.js-issue-time-estimate').text()).toContain('0m');
+ expect(findIssueTimeEstimate().text()).toContain('0m');
});
});
@@ -63,7 +65,7 @@ describe('Issue Time Estimate component', () => {
});
it('renders expanded time estimate in tooltip', () => {
- expect(wrapper.find('.js-issue-time-estimate').text()).toContain('104 hours 1 minute');
+ expect(findIssueTimeEstimate().text()).toContain('104 hours 1 minute');
});
});
});
diff --git a/spec/frontend/boards/components/item_count_spec.js b/spec/frontend/boards/components/item_count_spec.js
index 45980c36f1c..06cd3910fc0 100644
--- a/spec/frontend/boards/components/item_count_spec.js
+++ b/spec/frontend/boards/components/item_count_spec.js
@@ -29,7 +29,7 @@ describe('IssueCount', () => {
});
it('does not contains maxIssueCount in the template', () => {
- expect(vm.find('.js-max-issue-size').exists()).toBe(false);
+ expect(vm.find('.max-issue-size').exists()).toBe(false);
});
});
@@ -50,7 +50,7 @@ describe('IssueCount', () => {
});
it('contains maxIssueCount in the template', () => {
- expect(vm.find('.js-max-issue-size').text()).toEqual(String(maxIssueCount));
+ expect(vm.find('.max-issue-size').text()).toEqual(String(maxIssueCount));
});
it('does not have text-danger class when issueSize is less than maxIssueCount', () => {
@@ -75,7 +75,7 @@ describe('IssueCount', () => {
});
it('contains maxIssueCount in the template', () => {
- expect(vm.find('.js-max-issue-size').text()).toEqual(String(maxIssueCount));
+ expect(vm.find('.max-issue-size').text()).toEqual(String(maxIssueCount));
});
it('has text-danger class', () => {
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js
index c91a9c0f0fb..7d09c09d03b 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js
@@ -1,4 +1,4 @@
-import { GlSprintf } from '@gitlab/ui';
+import { GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Component from '~/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue';
@@ -6,6 +6,7 @@ import {
CONTAINER_REGISTRY_TITLE,
LIST_INTRO_TEXT,
EXPIRATION_POLICY_DISABLED_TEXT,
+ SET_UP_CLEANUP,
} from '~/packages_and_registries/container_registry/explorer/constants';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
@@ -21,6 +22,7 @@ describe('registry_header', () => {
const findCommandsSlot = () => wrapper.find('[data-testid="commands-slot"]');
const findImagesCountSubHeader = () => wrapper.find('[data-testid="images-count"]');
const findExpirationPolicySubHeader = () => wrapper.find('[data-testid="expiration-policy"]');
+ const findSetupCleanUpLink = () => wrapper.findComponent(GlLink);
const mountComponent = async (propsData, slots) => {
wrapper = shallowMount(Component, {
@@ -88,6 +90,7 @@ describe('registry_header', () => {
});
const text = findExpirationPolicySubHeader();
+
expect(text.exists()).toBe(true);
expect(text.props()).toMatchObject({
text: EXPIRATION_POLICY_DISABLED_TEXT,
@@ -100,12 +103,17 @@ describe('registry_header', () => {
await mountComponent({
expirationPolicy: { enabled: true },
expirationPolicyHelpPagePath: 'foo',
+ showCleanupPolicyLink: true,
imagesCount: 1,
});
const text = findExpirationPolicySubHeader();
+ const cleanupLink = findSetupCleanUpLink();
+
expect(text.exists()).toBe(true);
expect(text.props('text')).toBe('Expiration policy will run in ');
+ expect(cleanupLink.exists()).toBe(true);
+ expect(cleanupLink.text()).toBe(SET_UP_CLEANUP);
});
it('when the expiration policy is completely disabled', async () => {
await mountComponent({
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js
index da4bfcde217..79403d29d18 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js
@@ -6,7 +6,6 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import getContainerRepositoriesQuery from 'shared_queries/container_registry/get_container_repositories.query.graphql';
-import CleanupPolicyEnabledAlert from '~/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue';
import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants';
import DeleteImage from '~/packages_and_registries/container_registry/explorer/components/delete_image.vue';
import CliCommands from '~/packages_and_registries/shared/components/cli_commands.vue';
@@ -58,7 +57,6 @@ describe('List Page', () => {
const findPersistedSearch = () => wrapper.findComponent(PersistedSearch);
const findEmptySearchMessage = () => wrapper.find('[data-testid="emptySearch"]');
const findDeleteImage = () => wrapper.findComponent(DeleteImage);
- const findCleanupAlert = () => wrapper.findComponent(CleanupPolicyEnabledAlert);
const fireFirstSortUpdate = () => {
findPersistedSearch().vm.$emit('update', { sort: 'UPDATED_DESC', filters: [] });
@@ -511,33 +509,4 @@ describe('List Page', () => {
testTrackingCall('confirm_delete');
});
});
-
- describe('cleanup is on alert', () => {
- it('exist when showCleanupPolicyOnAlert is true and has the correct props', async () => {
- mountComponent({
- config: {
- showCleanupPolicyOnAlert: true,
- projectPath: 'foo',
- isGroupPage: false,
- cleanupPoliciesSettingsPath: 'bar',
- },
- });
-
- await waitForApolloRequestRender();
-
- expect(findCleanupAlert().exists()).toBe(true);
- expect(findCleanupAlert().props()).toMatchObject({
- projectPath: 'foo',
- cleanupPoliciesSettingsPath: 'bar',
- });
- });
-
- it('is hidden when showCleanupPolicyOnAlert is false', async () => {
- mountComponent();
-
- await waitForApolloRequestRender();
-
- expect(findCleanupAlert().exists()).toBe(false);
- });
- });
});
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js
index a6c929844b1..0a72f0269ee 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js
@@ -12,7 +12,6 @@ import {
UNAVAILABLE_USER_FEATURE_TEXT,
} from '~/packages_and_registries/settings/project/constants';
import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
-import CleanupPolicyEnabledAlert from '~/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue';
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
import {
@@ -31,12 +30,11 @@ describe('Registry Settings App', () => {
adminSettingsPath: 'settingsPath',
enableHistoricEntries: false,
helpPagePath: 'helpPagePath',
- showCleanupPolicyOnAlert: false,
+ showCleanupPolicyLink: false,
};
const findSettingsComponent = () => wrapper.find(SettingsForm);
const findAlert = () => wrapper.find(GlAlert);
- const findCleanupAlert = () => wrapper.findComponent(CleanupPolicyEnabledAlert);
const mountComponent = (provide = defaultProvidedValues, config) => {
wrapper = shallowMount(component, {
@@ -69,26 +67,6 @@ describe('Registry Settings App', () => {
wrapper.destroy();
});
- describe('cleanup is on alert', () => {
- it('exist when showCleanupPolicyOnAlert is true and has the correct props', () => {
- mountComponent({
- ...defaultProvidedValues,
- showCleanupPolicyOnAlert: true,
- });
-
- expect(findCleanupAlert().exists()).toBe(true);
- expect(findCleanupAlert().props()).toMatchObject({
- projectPath: 'path',
- });
- });
-
- it('is hidden when showCleanupPolicyOnAlert is false', async () => {
- mountComponent();
-
- expect(findCleanupAlert().exists()).toBe(false);
- });
- });
-
describe('isEdited status', () => {
it.each`
description | apiResponse | workingCopy | result
diff --git a/spec/frontend/packages_and_registries/shared/components/__snapshots__/cleanup_policy_enabled_alert_spec.js.snap b/spec/frontend/packages_and_registries/shared/components/__snapshots__/cleanup_policy_enabled_alert_spec.js.snap
deleted file mode 100644
index 2cded2ead2e..00000000000
--- a/spec/frontend/packages_and_registries/shared/components/__snapshots__/cleanup_policy_enabled_alert_spec.js.snap
+++ /dev/null
@@ -1,19 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`CleanupPolicyEnabledAlert renders 1`] = `
-<gl-alert-stub
- class="gl-mt-2"
- dismissible="true"
- dismisslabel="Dismiss"
- primarybuttonlink=""
- primarybuttontext=""
- secondarybuttonlink=""
- secondarybuttontext=""
- title=""
- variant="info"
->
- <gl-sprintf-stub
- message="Cleanup policies are now available for this project. %{linkStart}Click here to get started.%{linkEnd}"
- />
-</gl-alert-stub>
-`;
diff --git a/spec/frontend/packages_and_registries/shared/components/cleanup_policy_enabled_alert_spec.js b/spec/frontend/packages_and_registries/shared/components/cleanup_policy_enabled_alert_spec.js
deleted file mode 100644
index 269e087f5ac..00000000000
--- a/spec/frontend/packages_and_registries/shared/components/cleanup_policy_enabled_alert_spec.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import { GlAlert } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import component from '~/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue';
-import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
-
-describe('CleanupPolicyEnabledAlert', () => {
- let wrapper;
-
- const defaultProps = {
- projectPath: 'foo',
- cleanupPoliciesSettingsPath: 'label-bar',
- };
-
- const findAlert = () => wrapper.findComponent(GlAlert);
-
- const mountComponent = (props) => {
- wrapper = shallowMount(component, {
- stubs: {
- LocalStorageSync,
- },
- propsData: {
- ...defaultProps,
- ...props,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders', () => {
- mountComponent();
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('when dismissed is not visible', async () => {
- mountComponent();
-
- expect(findAlert().exists()).toBe(true);
- findAlert().vm.$emit('dismiss');
-
- await nextTick();
-
- expect(findAlert().exists()).toBe(false);
- });
-});
diff --git a/spec/helpers/packages_helper_spec.rb b/spec/helpers/packages_helper_spec.rb
index d7be4194e67..fc69aee4e04 100644
--- a/spec/helpers/packages_helper_spec.rb
+++ b/spec/helpers/packages_helper_spec.rb
@@ -65,11 +65,11 @@ RSpec.describe PackagesHelper do
end
end
- describe '#show_cleanup_policy_on_alert' do
+ describe '#show_cleanup_policy_link' do
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:container_repository) { create(:container_repository) }
- subject { helper.show_cleanup_policy_on_alert(project.reload) }
+ subject { helper.show_cleanup_policy_link(project.reload) }
where(:com, :config_registry, :project_registry, :nil_policy, :container_repositories_exist, :expected_result) do
false | false | false | false | false | false
diff --git a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
new file mode 100644
index 00000000000..d5151dd6beb
--- /dev/null
+++ b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
+ include StubVersion
+ using RSpec::Parameterized::TableSyntax
+
+ describe '#check_runner_upgrade_status' do
+ subject(:result) { described_class.instance.check_runner_upgrade_status(runner_version) }
+
+ before do
+ runner_releases_double = instance_double(Gitlab::Ci::RunnerReleases)
+
+ allow(Gitlab::Ci::RunnerReleases).to receive(:instance).and_return(runner_releases_double)
+ allow(runner_releases_double).to receive(:releases).and_return(available_runner_releases.map { |v| ::Gitlab::VersionInfo.parse(v) })
+ end
+
+ context 'with available_runner_releases configured up to 14.1.1' do
+ let(:available_runner_releases) { %w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2 14.1.0 14.1.1] }
+
+ context 'with nil runner_version' do
+ let(:runner_version) { nil }
+
+ it 'raises :unknown' do
+ is_expected.to eq(:unknown)
+ end
+ end
+
+ context 'with invalid runner_version' do
+ let(:runner_version) { 'junk' }
+
+ it 'raises ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+
+ context 'with Gitlab::VERSION set to 14.1.123' do
+ before do
+ stub_version('14.1.123', 'deadbeef')
+
+ described_class.instance.reset!
+ end
+
+ context 'with a runner_version that is too recent' do
+ let(:runner_version) { 'v14.2.0' }
+
+ it 'returns :not_available' do
+ is_expected.to eq(:not_available)
+ end
+ end
+ end
+
+ context 'with Gitlab::VERSION set to 14.0.123' do
+ before do
+ stub_version('14.0.123', 'deadbeef')
+
+ described_class.instance.reset!
+ end
+
+ context 'with valid params' do
+ where(:runner_version, :expected_result) do
+ 'v14.1.0' | :not_available # not available since the GitLab instance is still on 14.0.x
+ 'v14.0.1' | :recommended # recommended upgrade since 14.0.2 is available
+ 'v14.0.2' | :not_available # not available since 14.0.2 is the latest 14.0.x release available
+ 'v13.10.1' | :available # available upgrade: 14.1.1
+ 'v13.10.0' | :recommended # recommended upgrade since 13.10.1 is available
+ 'v13.9.2' | :recommended # recommended upgrade since backports are no longer released for this version
+ 'v13.9.0' | :recommended # recommended upgrade since backports are no longer released for this version
+ 'v13.8.1' | :recommended # recommended upgrade since build is too old (missing in records)
+ 'v11.4.1' | :recommended # recommended upgrade since build is too old (missing in records)
+ end
+
+ with_them do
+ it 'returns symbol representing expected upgrade status' do
+ is_expected.to be_a(Symbol)
+ is_expected.to eq(expected_result)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/object_counter_spec.rb b/spec/lib/gitlab/github_import/object_counter_spec.rb
index c9e4ac67061..e522f74416c 100644
--- a/spec/lib/gitlab/github_import/object_counter_spec.rb
+++ b/spec/lib/gitlab/github_import/object_counter_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::ObjectCounter, :clean_gitlab_redis_cache do
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { create(:project, :import_started, import_type: 'github') }
it 'validates the operation being incremented' do
expect { described_class.increment(project, :issue, :unknown) }
@@ -49,4 +49,12 @@ RSpec.describe Gitlab::GithubImport::ObjectCounter, :clean_gitlab_redis_cache do
'imported' => {}
})
end
+
+ it 'expires etag cache of relevant realtime change endpoints on increment' do
+ expect_next_instance_of(Gitlab::EtagCaching::Store) do |instance|
+ expect(instance).to receive(:touch).with(Gitlab::Routing.url_helpers.realtime_changes_import_github_path(format: :json))
+ end
+
+ described_class.increment(project, :issue, :fetched)
+ end
end
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 0ca918c898a..e937851392b 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -139,7 +139,8 @@ RSpec.describe ContainerRepository, :aggregate_failures do
expect { subject }
.to change { repository.reload.migration_state }.to('import_done')
- .and change { repository.reload.migration_skipped_reason }.to('not_found')
+ .and change { repository.migration_skipped_reason }.to('not_found')
+ .and change { repository.migration_import_done_at }.from(nil)
end
end
end
diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb
index 6d84b2c6931..40cf2bf21a3 100644
--- a/spec/models/project_import_state_spec.rb
+++ b/spec/models/project_import_state_spec.rb
@@ -101,6 +101,34 @@ RSpec.describe ProjectImportState, type: :model do
end
end
+ describe '#expire_etag_cache' do
+ context 'when project import type has realtime changes endpoint' do
+ before do
+ import_state.project.import_type = 'github'
+ end
+
+ it 'expires revelant etag cache' do
+ expect_next_instance_of(Gitlab::EtagCaching::Store) do |instance|
+ expect(instance).to receive(:touch).with(Gitlab::Routing.url_helpers.realtime_changes_import_github_path(format: :json))
+ end
+
+ subject.expire_etag_cache
+ end
+ end
+
+ context 'when project import type does not have realtime changes endpoint' do
+ before do
+ import_state.project.import_type = 'jira'
+ end
+
+ it 'does not touch etag caches' do
+ expect(Gitlab::EtagCaching::Store).not_to receive(:new)
+
+ subject.expire_etag_cache
+ end
+ end
+ end
+
describe 'import state transitions' do
context 'state transition: [:started] => [:finished]' do
let(:after_import_service) { spy(:after_import_service) }
@@ -178,4 +206,20 @@ RSpec.describe ProjectImportState, type: :model do
end
end
end
+
+ describe 'callbacks' do
+ context 'after_commit :expire_etag_cache' do
+ before do
+ import_state.project.import_type = 'github'
+ end
+
+ it 'expires etag cache' do
+ expect_next_instance_of(Gitlab::EtagCaching::Store) do |instance|
+ expect(instance).to receive(:touch).with(Gitlab::Routing.url_helpers.realtime_changes_import_github_path(format: :json))
+ end
+
+ subject.save!
+ end
+ end
+ end
end
diff --git a/spec/services/incident_management/issuable_escalation_statuses/build_service_spec.rb b/spec/services/incident_management/issuable_escalation_statuses/build_service_spec.rb
new file mode 100644
index 00000000000..c20a0688ac2
--- /dev/null
+++ b/spec/services/incident_management/issuable_escalation_statuses/build_service_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IncidentManagement::IssuableEscalationStatuses::BuildService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:incident, reload: true) { create(:incident, project: project) }
+
+ let(:service) { described_class.new(incident) }
+
+ subject(:execute) { service.execute }
+
+ it_behaves_like 'initializes new escalation status with expected attributes'
+
+ context 'with associated alert' do
+ let_it_be(:alert) { create(:alert_management_alert, :acknowledged, project: project, issue: incident) }
+
+ it_behaves_like 'initializes new escalation status with expected attributes', { status_event: :acknowledge }
+ end
+end
diff --git a/spec/services/incident_management/issuable_escalation_statuses/create_service_spec.rb b/spec/services/incident_management/issuable_escalation_statuses/create_service_spec.rb
index 8fbab361ec4..2c7d330766c 100644
--- a/spec/services/incident_management/issuable_escalation_statuses/create_service_spec.rb
+++ b/spec/services/incident_management/issuable_escalation_statuses/create_service_spec.rb
@@ -8,12 +8,12 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService do
let(:incident) { create(:incident, project: project) }
let(:service) { described_class.new(incident) }
- subject(:execute) { service.execute}
+ subject(:execute) { service.execute }
it 'creates an escalation status for the incident with no policy set' do
- expect { execute }.to change { incident.reload.incident_management_issuable_escalation_status }.from(nil)
+ expect { execute }.to change { incident.reload.escalation_status }.from(nil)
- status = incident.incident_management_issuable_escalation_status
+ status = incident.escalation_status
expect(status.policy_id).to eq(nil)
expect(status.escalations_started_at).to eq(nil)
@@ -24,7 +24,22 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService do
let!(:existing_status) { create(:incident_management_issuable_escalation_status, issue: incident) }
it 'exits without changing anything' do
- expect { execute }.not_to change { incident.reload.incident_management_issuable_escalation_status }
+ expect { execute }.not_to change { incident.reload.escalation_status }
+ end
+ end
+
+ context 'with associated alert' do
+ before do
+ create(:alert_management_alert, :acknowledged, project: project, issue: incident)
+ end
+
+ it 'creates an escalation status matching the alert attributes' do
+ expect { execute }.to change { incident.reload.escalation_status }.from(nil)
+ expect(incident.escalation_status).to have_attributes(
+ status_name: :acknowledged,
+ policy_id: nil,
+ escalations_started_at: nil
+ )
end
end
end
diff --git a/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb b/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb
index b30b3a69ae6..25164df40ca 100644
--- a/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb
+++ b/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb
@@ -71,7 +71,12 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::PrepareUpdateServ
context 'when an IssuableEscalationStatus record for the issue does not exist' do
let(:issue) { create(:incident) }
- it_behaves_like 'availability error response'
+ it_behaves_like 'successful response', { status_event: :acknowledge }
+
+ it 'initializes an issuable escalation status record' do
+ expect { result }.not_to change(::IncidentManagement::IssuableEscalationStatus, :count)
+ expect(issue.escalation_status).to be_present
+ end
end
context 'when called without params' do
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 6d3c3dd4e39..d8e388b1deb 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -1224,6 +1224,18 @@ RSpec.describe Issues::UpdateService, :mailer do
end
context 'without an escalation status record' do
+ it 'creates a new record' do
+ expect { update_issue(opts) }.to change(::IncidentManagement::IssuableEscalationStatus, :count).by(1)
+ end
+
+ it_behaves_like 'updates the escalation status record', :acknowledged
+ end
+
+ context 'with :incident_escalations feature flag disabled' do
+ before do
+ stub_feature_flags(incident_escalations: false)
+ end
+
it_behaves_like 'does not change the status record'
end
end
diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
index 1e303197990..15590fd10dc 100644
--- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -138,7 +138,7 @@ RSpec.shared_examples 'multiple issue boards' do
wait_for_requests
- dropdown_selector = '.js-boards-selector .dropdown-menu'
+ dropdown_selector = '[data-testid="boards-selector"] .dropdown-menu'
page.within(dropdown_selector) do
yield
end
diff --git a/spec/support/shared_examples/incident_management/issuable_escalation_statuses/build_examples.rb b/spec/support/shared_examples/incident_management/issuable_escalation_statuses/build_examples.rb
new file mode 100644
index 00000000000..050fdc3fff7
--- /dev/null
+++ b/spec/support/shared_examples/incident_management/issuable_escalation_statuses/build_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'initializes new escalation status with expected attributes' do |attributes = {}|
+ let(:expected_attributes) { attributes }
+
+ specify do
+ expect { execute }.to change { incident.escalation_status }
+ .from(nil)
+ .to(instance_of(::IncidentManagement::IssuableEscalationStatus))
+
+ expect(incident.escalation_status).to have_attributes(
+ id: nil,
+ issue_id: incident.id,
+ policy_id: nil,
+ escalations_started_at: nil,
+ status_event: nil,
+ **expected_attributes
+ )
+ end
+end
diff --git a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
index 34073d0ea39..af15f465107 100644
--- a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::GithubImport::ImportDiffNoteWorker do
describe '#import' do
it 'imports a diff note' do
- project = double(:project, full_path: 'foo/bar', id: 1)
+ project = double(:project, full_path: 'foo/bar', id: 1, import_state: nil)
client = double(:client)
importer = double(:importer)
hash = {
diff --git a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
index dc0338eccad..29f21c1d184 100644
--- a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::GithubImport::ImportIssueWorker do
describe '#import' do
it 'imports an issue' do
- project = double(:project, full_path: 'foo/bar', id: 1)
+ project = double(:project, full_path: 'foo/bar', id: 1, import_state: nil)
client = double(:client)
importer = double(:importer)
hash = {
diff --git a/spec/workers/gitlab/github_import/import_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
index bc254e6246d..f4598340938 100644
--- a/spec/workers/gitlab/github_import/import_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::GithubImport::ImportNoteWorker do
describe '#import' do
it 'imports a note' do
- project = double(:project, full_path: 'foo/bar', id: 1)
+ project = double(:project, full_path: 'foo/bar', id: 1, import_state: nil)
client = double(:client)
importer = double(:importer)
hash = {
diff --git a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
index 6fe9741075f..faed2f8f340 100644
--- a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::GithubImport::ImportPullRequestWorker do
describe '#import' do
it 'imports a pull request' do
- project = double(:project, full_path: 'foo/bar', id: 1)
+ project = double(:project, full_path: 'foo/bar', id: 1, import_state: nil)
client = double(:client)
importer = double(:importer)
hash = {