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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api/groups_api.js14
-rw-r--r--app/assets/javascripts/boards/components/board_content_sidebar.vue40
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_link_button.vue7
-rw-r--r--app/assets/javascripts/content_editor/components/top_toolbar.vue6
-rw-r--r--app/assets/javascripts/invite_members/components/group_select.vue30
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_modal.vue19
-rw-r--r--app/assets/javascripts/invite_members/constants.js5
-rw-r--r--app/assets/javascripts/invite_members/init_invite_members_modal.js2
-rw-r--r--app/assets/javascripts/issues_list/components/issues_list_app.vue15
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue88
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js4
-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/components/interval_pattern_input.vue46
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js2
-rw-r--r--app/assets/javascripts/registry/explorer/index.js2
-rw-r--r--app/assets/javascripts/registry/explorer/pages/list.vue8
-rw-r--r--app/assets/javascripts/security_configuration/components/redesigned_app.vue15
-rw-r--r--app/assets/javascripts/security_configuration/components/upgrade_banner.vue45
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue40
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue19
-rw-r--r--app/controllers/groups/boards_controller.rb1
-rw-r--r--app/controllers/groups_controller.rb1
-rw-r--r--app/controllers/projects/boards_controller.rb1
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/controllers/projects/pipeline_schedules_controller.rb4
-rw-r--r--app/graphql/mutations/ci/runners_registration_token/reset.rb66
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/helpers/invite_members_helper.rb8
-rw-r--r--app/helpers/nav/top_nav_helper.rb6
-rw-r--r--app/helpers/packages_helper.rb10
-rw-r--r--app/models/ci/job_artifact.rb16
-rw-r--r--app/models/ci/pipeline_schedule.rb4
-rw-r--r--app/models/project.rb8
-rw-r--r--app/models/user_callout.rb3
-rw-r--r--app/policies/global_policy.rb1
-rw-r--r--app/policies/group_policy.rb1
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/services/ci/pipeline_schedules/calculate_next_run_service.rb6
-rw-r--r--app/services/web_hook_service.rb27
-rw-r--r--app/views/admin/application_settings/_diff_limits.html.haml4
-rw-r--r--app/views/admin/broadcast_messages/_form.html.haml14
-rw-r--r--app/views/admin/groups/_form.html.haml10
-rw-r--r--app/views/groups/_invite_members_modal.html.haml2
-rw-r--r--app/views/groups/group_members/index.html.haml2
-rw-r--r--app/views/layouts/nav/groups_dropdown/_show.html.haml13
-rw-r--r--app/views/projects/commits/_commits.html.haml6
-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/ci_cd/show.html.haml12
-rw-r--r--app/views/projects/settings/packages_and_registries/show.html.haml23
-rw-r--r--app/workers/all_queues.yml4
-rw-r--r--app/workers/expire_pipeline_cache_worker.rb8
-rw-r--r--app/workers/pipeline_process_worker.rb6
54 files changed, 580 insertions, 164 deletions
diff --git a/app/assets/javascripts/api/groups_api.js b/app/assets/javascripts/api/groups_api.js
index d4ba46656e6..d6c9e1d42cc 100644
--- a/app/assets/javascripts/api/groups_api.js
+++ b/app/assets/javascripts/api/groups_api.js
@@ -3,9 +3,9 @@ import { buildApiUrl } from './api_utils';
import { DEFAULT_PER_PAGE } from './constants';
const GROUPS_PATH = '/api/:version/groups.json';
+const DESCENDANT_GROUPS_PATH = '/api/:version/groups/:id/descendant_groups';
-export function getGroups(query, options, callback = () => {}) {
- const url = buildApiUrl(GROUPS_PATH);
+const axiosGet = (url, query, options, callback) => {
return axios
.get(url, {
params: {
@@ -19,4 +19,14 @@ export function getGroups(query, options, callback = () => {}) {
return data;
});
+};
+
+export function getGroups(query, options, callback = () => {}) {
+ const url = buildApiUrl(GROUPS_PATH);
+ return axiosGet(url, query, options, callback);
+}
+
+export function getDescendentGroups(parentGroupId, query, options, callback = () => {}) {
+ const url = buildApiUrl(DESCENDANT_GROUPS_PATH.replace(':id', parentGroupId));
+ return axiosGet(url, query, options, callback);
}
diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue
index f83927ea4e7..16a8a9d253f 100644
--- a/app/assets/javascripts/boards/components/board_content_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue
@@ -11,6 +11,7 @@ import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assig
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
headerHeight: `${contentTop()}px`,
@@ -26,7 +27,10 @@ export default {
SidebarDropdownWidget,
BoardSidebarWeightInput: () =>
import('ee_component/boards/components/sidebar/board_sidebar_weight_input.vue'),
+ IterationSidebarDropdownWidget: () =>
+ import('ee_component/sidebar/components/iteration_sidebar_dropdown_widget.vue'),
},
+ mixins: [glFeatureFlagMixin()],
inject: {
multipleAssigneesFeatureAvailable: {
default: false,
@@ -103,17 +107,31 @@ export default {
:issuable-type="issuableType"
data-testid="sidebar-milestones"
/>
- <sidebar-dropdown-widget
- v-if="iterationFeatureAvailable"
- :iid="activeBoardItem.iid"
- issuable-attribute="iteration"
- :workspace-path="projectPathForActiveIssue"
- :attr-workspace-path="groupPathForActiveIssue"
- :issuable-type="issuableType"
- class="gl-mt-5"
- data-testid="iteration-edit"
- data-qa-selector="iteration_container"
- />
+ <template v-if="!glFeatures.iterationCadences">
+ <sidebar-dropdown-widget
+ v-if="iterationFeatureAvailable"
+ :iid="activeBoardItem.iid"
+ issuable-attribute="iteration"
+ :workspace-path="projectPathForActiveIssue"
+ :attr-workspace-path="groupPathForActiveIssue"
+ :issuable-type="issuableType"
+ class="gl-mt-5"
+ data-testid="iteration-edit"
+ data-qa-selector="iteration_container"
+ />
+ </template>
+ <template v-else>
+ <iteration-sidebar-dropdown-widget
+ v-if="iterationFeatureAvailable"
+ :iid="activeBoardItem.iid"
+ :workspace-path="projectPathForActiveIssue"
+ :attr-workspace-path="groupPathForActiveIssue"
+ :issuable-type="issuableType"
+ class="gl-mt-5"
+ data-testid="iteration-edit"
+ data-qa-selector="iteration_container"
+ />
+ </template>
</div>
<board-sidebar-time-tracker class="swimlanes-sidebar-time-tracker" />
<sidebar-date-widget
diff --git a/app/assets/javascripts/content_editor/components/toolbar_link_button.vue b/app/assets/javascripts/content_editor/components/toolbar_link_button.vue
index 369eadceff9..f706080eaa1 100644
--- a/app/assets/javascripts/content_editor/components/toolbar_link_button.vue
+++ b/app/assets/javascripts/content_editor/components/toolbar_link_button.vue
@@ -6,6 +6,7 @@ import {
GlFormInputGroup,
GlDropdownDivider,
GlDropdownItem,
+ GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
import { Editor as TiptapEditor } from '@tiptap/vue-2';
import { hasSelection } from '../services/utils';
@@ -21,6 +22,9 @@ export default {
GlDropdownItem,
GlButton,
},
+ directives: {
+ GlTooltip,
+ },
props: {
tiptapEditor: {
type: TiptapEditor,
@@ -68,6 +72,9 @@ export default {
</script>
<template>
<gl-dropdown
+ v-gl-tooltip
+ :aria-label="__('Insert link')"
+ :title="__('Insert link')"
:toggle-class="{ active: isActive }"
size="small"
category="tertiary"
diff --git a/app/assets/javascripts/content_editor/components/top_toolbar.vue b/app/assets/javascripts/content_editor/components/top_toolbar.vue
index 4da230cbde6..07fdd3147e2 100644
--- a/app/assets/javascripts/content_editor/components/top_toolbar.vue
+++ b/app/assets/javascripts/content_editor/components/top_toolbar.vue
@@ -72,7 +72,11 @@ export default {
:tiptap-editor="contentEditor.tiptapEditor"
@execute="trackToolbarControlExecution"
/>
- <toolbar-link-button :tiptap-editor="contentEditor.tiptapEditor" />
+ <toolbar-link-button
+ data-testid="link"
+ :tiptap-editor="contentEditor.tiptapEditor"
+ @execute="trackToolbarControlExecution"
+ />
<divider />
<toolbar-button
data-testid="blockquote"
diff --git a/app/assets/javascripts/invite_members/components/group_select.vue b/app/assets/javascripts/invite_members/components/group_select.vue
index bac8914c374..2d1e57a1177 100644
--- a/app/assets/javascripts/invite_members/components/group_select.vue
+++ b/app/assets/javascripts/invite_members/components/group_select.vue
@@ -7,9 +7,9 @@ import {
GlSearchBoxByType,
} from '@gitlab/ui';
import { debounce } from 'lodash';
-import Api from '~/api';
import { s__ } from '~/locale';
-import { SEARCH_DELAY } from '../constants';
+import { getGroups, getDescendentGroups } from '~/rest_api';
+import { SEARCH_DELAY, GROUP_FILTERS } from '../constants';
export default {
name: 'GroupSelect',
@@ -23,6 +23,18 @@ export default {
model: {
prop: 'selectedGroup',
},
+ props: {
+ groupsFilter: {
+ type: String,
+ required: false,
+ default: GROUP_FILTERS.ALL,
+ },
+ parentGroupId: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ },
data() {
return {
isFetching: false,
@@ -50,7 +62,7 @@ export default {
methods: {
retrieveGroups: debounce(function debouncedRetrieveGroups() {
this.isFetching = true;
- return Api.groups(this.searchTerm, this.$options.defaultFetchOptions)
+ return this.fetchGroups()
.then((response) => {
this.groups = response.map((group) => ({
id: group.id,
@@ -69,6 +81,18 @@ export default {
this.$emit('input', this.selectedGroup);
},
+ fetchGroups() {
+ switch (this.groupsFilter) {
+ case GROUP_FILTERS.DESCENDANT_GROUPS:
+ return getDescendentGroups(
+ this.parentGroupId,
+ this.searchTerm,
+ this.$options.defaultFetchOptions,
+ );
+ default:
+ return getGroups(this.searchTerm, this.$options.defaultFetchOptions);
+ }
+ },
},
i18n: {
dropdownText: s__('GroupSelect|Select a group'),
diff --git a/app/assets/javascripts/invite_members/components/invite_members_modal.vue b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
index 0db464bb657..84c8594c6b6 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
@@ -16,7 +16,7 @@ import GroupSelect from '~/invite_members/components/group_select.vue';
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
import { s__, sprintf } from '~/locale';
-import { INVITE_MEMBERS_IN_COMMENT } from '../constants';
+import { INVITE_MEMBERS_IN_COMMENT, GROUP_FILTERS } from '../constants';
import eventHub from '../event_hub';
export default {
@@ -54,6 +54,16 @@ export default {
type: Number,
required: true,
},
+ groupSelectFilter: {
+ type: String,
+ required: false,
+ default: GROUP_FILTERS.ALL,
+ },
+ groupSelectParentId: {
+ type: Number,
+ required: false,
+ default: null,
+ },
helpLink: {
type: String,
required: true,
@@ -293,7 +303,12 @@ export default {
:aria-labelledby="$options.membersTokenSelectLabelId"
:placeholder="$options.labels[inviteeType].placeHolder"
/>
- <group-select v-if="isInviteGroup" v-model="groupToBeSharedWith" />
+ <group-select
+ v-if="isInviteGroup"
+ v-model="groupToBeSharedWith"
+ :groups-filter="groupSelectFilter"
+ :parent-group-id="groupSelectParentId"
+ />
</div>
<label class="gl-font-weight-bold gl-mt-3">{{ $options.labels.accessLevel }}</label>
diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js
index a651b81c60e..0c5538d5b86 100644
--- a/app/assets/javascripts/invite_members/constants.js
+++ b/app/assets/javascripts/invite_members/constants.js
@@ -1,3 +1,8 @@
export const SEARCH_DELAY = 200;
export const INVITE_MEMBERS_IN_COMMENT = 'invite_members_in_comment';
+
+export const GROUP_FILTERS = {
+ ALL: 'all',
+ DESCENDANT_GROUPS: 'descendant_groups',
+};
diff --git a/app/assets/javascripts/invite_members/init_invite_members_modal.js b/app/assets/javascripts/invite_members/init_invite_members_modal.js
index fc77bd53ba4..7501e9f4e6e 100644
--- a/app/assets/javascripts/invite_members/init_invite_members_modal.js
+++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js
@@ -21,6 +21,8 @@ export default function initInviteMembersModal() {
isProject: parseBoolean(el.dataset.isProject),
accessLevels: JSON.parse(el.dataset.accessLevels),
defaultAccessLevel: parseInt(el.dataset.defaultAccessLevel, 10),
+ groupSelectFilter: el.dataset.groupsFilter,
+ groupSelectParentId: parseInt(el.dataset.parentId, 10),
},
}),
});
diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue
index 4ba2bc3a8e3..d5cab77f26c 100644
--- a/app/assets/javascripts/issues_list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue
@@ -205,6 +205,19 @@ export default {
return convertToSearchQuery(this.filterTokens) || undefined;
},
searchTokens() {
+ let preloadedAuthors = [];
+
+ if (gon.current_user_id) {
+ preloadedAuthors = [
+ {
+ id: gon.current_user_id,
+ name: gon.current_user_fullname,
+ username: gon.current_username,
+ avatar_url: gon.current_user_avatar_url,
+ },
+ ];
+ }
+
const tokens = [
{
type: TOKEN_TYPE_AUTHOR,
@@ -215,6 +228,7 @@ export default {
unique: true,
defaultAuthors: [],
fetchAuthors: this.fetchUsers,
+ preloadedAuthors,
},
{
type: TOKEN_TYPE_ASSIGNEE,
@@ -225,6 +239,7 @@ export default {
unique: !this.hasMultipleIssueAssigneesFeature,
defaultAuthors: DEFAULT_NONE_ANY,
fetchAuthors: this.fetchUsers,
+ preloadedAuthors,
},
{
type: TOKEN_TYPE_MILESTONE,
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 edbe9441e57..6da2e3a47e8 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,17 +9,28 @@ 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';
export default {
components: {
+ SettingsBlock,
SettingsForm,
+ CleanupPolicyEnabledAlert,
GlAlert,
GlSprintf,
GlLink,
},
- inject: ['projectPath', 'isAdmin', 'adminSettingsPath', 'enableHistoricEntries'],
+ inject: [
+ 'projectPath',
+ 'isAdmin',
+ 'adminSettingsPath',
+ 'enableHistoricEntries',
+ 'helpPagePath',
+ 'showCleanupPolicyOnAlert',
+ ],
i18n: {
UNAVAILABLE_FEATURE_TITLE,
UNAVAILABLE_FEATURE_INTRO_TEXT,
@@ -75,32 +86,53 @@ export default {
</script>
<template>
- <div>
- <settings-form
- v-if="!isDisabled"
- v-model="workingCopy"
- :is-loading="$apollo.queries.containerExpirationPolicy.loading"
- :is-edited="isEdited"
- @reset="restoreOriginal"
- />
- <template v-else>
- <gl-alert
- v-if="showDisabledFormMessage"
- :dismissible="false"
- :title="$options.i18n.UNAVAILABLE_FEATURE_TITLE"
- variant="tip"
- >
- {{ $options.i18n.UNAVAILABLE_FEATURE_INTRO_TEXT }}
+ <section data-testid="registry-settings-app">
+ <cleanup-policy-enabled-alert v-if="showCleanupPolicyOnAlert" :project-path="projectPath" />
+ <settings-block default-expanded>
+ <template #title> {{ __('Clean up image tags') }}</template>
+ <template #description>
+ <span data-testid="description">
+ <gl-sprintf
+ :message="
+ __(
+ 'Save space and find images in the container Registry. remove unneeded tags and keep only the ones you want. %{linkStart}How does cleanup work?%{linkEnd}',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link :href="helpPagePath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </span>
+ </template>
+ <template #default>
+ <settings-form
+ v-if="!isDisabled"
+ v-model="workingCopy"
+ :is-loading="$apollo.queries.containerExpirationPolicy.loading"
+ :is-edited="isEdited"
+ @reset="restoreOriginal"
+ />
+ <template v-else>
+ <gl-alert
+ v-if="showDisabledFormMessage"
+ :dismissible="false"
+ :title="$options.i18n.UNAVAILABLE_FEATURE_TITLE"
+ variant="tip"
+ >
+ {{ $options.i18n.UNAVAILABLE_FEATURE_INTRO_TEXT }}
- <gl-sprintf :message="unavailableFeatureMessage">
- <template #link="{ content }">
- <gl-link :href="adminSettingsPath" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </gl-alert>
- <gl-alert v-else-if="fetchSettingsError" variant="warning" :dismissible="false">
- <gl-sprintf :message="$options.i18n.FETCH_SETTINGS_ERROR_MESSAGE" />
- </gl-alert>
- </template>
- </div>
+ <gl-sprintf :message="unavailableFeatureMessage">
+ <template #link="{ content }">
+ <gl-link :href="adminSettingsPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </gl-alert>
+ <gl-alert v-else-if="fetchSettingsError" variant="warning" :dismissible="false">
+ <gl-sprintf :message="$options.i18n.FETCH_SETTINGS_ERROR_MESSAGE" />
+ </gl-alert>
+ </template>
+ </template>
+ </settings-block>
+ </section>
</template>
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 65af6f846aa..2a3e2c28fa6 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
@@ -19,6 +19,8 @@ export default () => {
projectPath,
adminSettingsPath,
tagsRegexHelpPagePath,
+ helpPagePath,
+ showCleanupPolicyOnAlert,
} = el.dataset;
return new Vue({
el,
@@ -32,6 +34,8 @@ export default () => {
projectPath,
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
new file mode 100644
index 00000000000..d51c62e0623
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue
@@ -0,0 +1,54 @@
+<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/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
index 159c619e16c..d0ec5668d21 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
@@ -1,7 +1,15 @@
<script>
-import { GlFormRadio, GlFormRadioGroup, GlLink, GlSprintf } from '@gitlab/ui';
+import {
+ GlFormRadio,
+ GlFormRadioGroup,
+ GlIcon,
+ GlLink,
+ GlSprintf,
+ GlTooltipDirective,
+} from '@gitlab/ui';
import { getWeekdayNames } from '~/lib/utils/datetime_utility';
-import { s__, sprintf } from '~/locale';
+import { __, s__, sprintf } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
const KEY_EVERY_DAY = 'everyDay';
const KEY_EVERY_WEEK = 'everyWeek';
@@ -12,15 +20,25 @@ export default {
components: {
GlFormRadio,
GlFormRadioGroup,
+ GlIcon,
GlLink,
GlSprintf,
},
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [glFeatureFlagMixin()],
props: {
initialCronInterval: {
type: String,
required: false,
default: '',
},
+ dailyLimit: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
return {
@@ -80,6 +98,17 @@ export default {
weekday() {
return getWeekdayNames()[this.randomWeekDayIndex];
},
+ parsedDailyLimit() {
+ return this.dailyLimit ? (24 * 60) / this.dailyLimit : null;
+ },
+ scheduleDailyLimitMsg() {
+ return sprintf(
+ __(
+ 'Scheduled pipelines cannot run more frequently than once per %{limit} minutes. A pipeline configured to run more frequently only starts after %{limit} minutes have elapsed since the last time it ran.',
+ ),
+ { limit: this.parsedDailyLimit },
+ );
+ },
},
watch: {
cronInterval() {
@@ -111,6 +140,11 @@ export default {
generateRandomDay() {
return Math.floor(Math.random() * 28);
},
+ showDailyLimitMessage({ value }) {
+ return (
+ value === KEY_CUSTOM && this.glFeatures.ciDailyLimitForPipelineSchedules && this.dailyLimit
+ );
+ },
},
};
</script>
@@ -131,7 +165,15 @@ export default {
</gl-link>
</template>
</gl-sprintf>
+
<template v-else>{{ option.text }}</template>
+
+ <gl-icon
+ v-if="showDailyLimitMessage(option)"
+ v-gl-tooltip.hover
+ name="question"
+ :title="scheduleDailyLimitMsg"
+ />
</gl-form-radio>
</gl-form-radio-group>
<input
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 ce0e573fed2..9056c76d6ca 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
@@ -12,6 +12,7 @@ Vue.use(Translate);
function initIntervalPatternInput() {
const intervalPatternMount = document.getElementById('interval-pattern-input');
const initialCronInterval = intervalPatternMount?.dataset?.initialInterval;
+ const dailyLimit = intervalPatternMount.dataset?.dailyLimit;
return new Vue({
el: intervalPatternMount,
@@ -22,6 +23,7 @@ function initIntervalPatternInput() {
return createElement('interval-pattern-input', {
props: {
initialCronInterval,
+ dailyLimit,
},
});
},
diff --git a/app/assets/javascripts/registry/explorer/index.js b/app/assets/javascripts/registry/explorer/index.js
index f66839a74bf..1f82fd7f238 100644
--- a/app/assets/javascripts/registry/explorer/index.js
+++ b/app/assets/javascripts/registry/explorer/index.js
@@ -34,6 +34,7 @@ export default () => {
expirationPolicy,
isGroupPage,
isAdmin,
+ showCleanupPolicyOnAlert,
showUnfinishedTagCleanupCallout,
...config
} = el.dataset;
@@ -64,6 +65,7 @@ export default () => {
expirationPolicy: expirationPolicy ? JSON.parse(expirationPolicy) : undefined,
isGroupPage: parseBoolean(isGroupPage),
isAdmin: parseBoolean(isAdmin),
+ showCleanupPolicyOnAlert: parseBoolean(showCleanupPolicyOnAlert),
showUnfinishedTagCleanupCallout: parseBoolean(showUnfinishedTagCleanupCallout),
},
/* eslint-disable @gitlab/require-i18n-strings */
diff --git a/app/assets/javascripts/registry/explorer/pages/list.vue b/app/assets/javascripts/registry/explorer/pages/list.vue
index 589b88d7bbe..3c8790fa6e5 100644
--- a/app/assets/javascripts/registry/explorer/pages/list.vue
+++ b/app/assets/javascripts/registry/explorer/pages/list.vue
@@ -11,6 +11,7 @@ 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 { extractFilterAndSorting } from '~/packages_and_registries/shared/utils';
import Tracking from '~/tracking';
@@ -61,6 +62,7 @@ export default {
RegistryHeader,
DeleteImage,
RegistrySearch,
+ CleanupPolicyEnabledAlert,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -283,6 +285,12 @@ 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="config.characterError"
:title="$options.i18n.CONNECTION_ERROR_TITLE"
diff --git a/app/assets/javascripts/security_configuration/components/redesigned_app.vue b/app/assets/javascripts/security_configuration/components/redesigned_app.vue
index c2d57e8f0c8..d8a12f4a792 100644
--- a/app/assets/javascripts/security_configuration/components/redesigned_app.vue
+++ b/app/assets/javascripts/security_configuration/components/redesigned_app.vue
@@ -1,8 +1,10 @@
<script>
import { GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
+import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import FeatureCard from './feature_card.vue';
import SectionLayout from './section_layout.vue';
+import UpgradeBanner from './upgrade_banner.vue';
export const i18n = {
compliance: s__('SecurityConfiguration|Compliance'),
@@ -25,6 +27,8 @@ export default {
GlSprintf,
FeatureCard,
SectionLayout,
+ UpgradeBanner,
+ UserCalloutDismisser,
},
props: {
augmentedSecurityFeatures: {
@@ -52,6 +56,11 @@ export default {
},
},
computed: {
+ canUpgrade() {
+ return [...this.augmentedSecurityFeatures, ...this.augmentedComplianceFeatures].some(
+ ({ available }) => !available,
+ );
+ },
canViewCiHistory() {
return Boolean(this.gitlabCiPresent && this.gitlabCiHistoryPath);
},
@@ -65,6 +74,12 @@ export default {
<h1 class="gl-font-size-h1">{{ $options.i18n.securityConfiguration }}</h1>
</header>
+ <user-callout-dismisser v-if="canUpgrade" feature-name="security_configuration_upgrade_banner">
+ <template #default="{ dismiss, shouldShowCallout }">
+ <upgrade-banner v-if="shouldShowCallout" @close="dismiss" />
+ </template>
+ </user-callout-dismisser>
+
<gl-tabs content-class="gl-pt-6">
<gl-tab data-testid="security-testing-tab" :title="$options.i18n.securityTesting">
<section-layout :heading="$options.i18n.securityTesting">
diff --git a/app/assets/javascripts/security_configuration/components/upgrade_banner.vue b/app/assets/javascripts/security_configuration/components/upgrade_banner.vue
new file mode 100644
index 00000000000..ca0f9e5c85a
--- /dev/null
+++ b/app/assets/javascripts/security_configuration/components/upgrade_banner.vue
@@ -0,0 +1,45 @@
+<script>
+import { GlBanner } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ components: {
+ GlBanner,
+ },
+ inject: ['upgradePath'],
+ i18n: {
+ title: s__('SecurityConfiguration|Secure your project with Ultimate'),
+ bodyStart: s__(
+ `SecurityConfiguration|GitLab Ultimate checks your application for security vulnerabilities
+ that may lead to unauthorized access, data leaks, and denial of service
+ attacks. Its features include:`,
+ ),
+ bodyListItems: [
+ s__('SecurityConfiguration|Vulnerability details and statistics in the merge request.'),
+ s__('SecurityConfiguration|High-level vulnerability statistics across projects and groups.'),
+ s__('SecurityConfiguration|Runtime security metrics for application environments.'),
+ ],
+ bodyEnd: s__(
+ 'SecurityConfiguration|With the information provided, you can immediately begin risk analysis and remediation within GitLab.',
+ ),
+ buttonText: s__('SecurityConfiguration|Upgrade or start a free trial'),
+ },
+};
+</script>
+
+<template>
+ <gl-banner
+ :title="$options.i18n.title"
+ :button-text="$options.i18n.buttonText"
+ :button-link="upgradePath"
+ v-on="$listeners"
+ >
+ <p>{{ $options.i18n.bodyStart }}</p>
+ <ul>
+ <li v-for="bodyListItem in $options.i18n.bodyListItems" :key="bodyListItem">
+ {{ bodyListItem }}
+ </li>
+ </ul>
+ <p>{{ $options.i18n.bodyEnd }}</p>
+ </gl-banner>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
index 277e1400bf2..c80ccc928b3 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
@@ -293,9 +293,17 @@ export default {
<span v-else-if="!currentAttribute" class="gl-text-gray-500">
{{ $options.i18n.none }}
</span>
- <gl-link v-else class="gl-text-gray-900! gl-font-weight-bold" :href="attributeUrl">
- {{ attributeTitle }}
- </gl-link>
+ <slot
+ v-else
+ name="value"
+ :attributeTitle="attributeTitle"
+ :attributeUrl="attributeUrl"
+ :currentAttribute="currentAttribute"
+ >
+ <gl-link class="gl-text-gray-900! gl-font-weight-bold" :href="attributeUrl">
+ {{ attributeTitle }}
+ </gl-link>
+ </slot>
</div>
</template>
<template #default>
@@ -327,16 +335,24 @@ export default {
<gl-dropdown-text v-if="emptyPropsList">
{{ i18n.noAttributesFound }}
</gl-dropdown-text>
- <gl-dropdown-item
- v-for="attrItem in attributesList"
- :key="attrItem.id"
- :is-check-item="true"
- :is-checked="isAttributeChecked(attrItem.id)"
- :data-testid="`${issuableAttribute}-items`"
- @click="updateAttribute(attrItem.id)"
+ <slot
+ v-else
+ name="list"
+ :attributesList="attributesList"
+ :isAttributeChecked="isAttributeChecked"
+ :updateAttribute="updateAttribute"
>
- {{ attrItem.title }}
- </gl-dropdown-item>
+ <gl-dropdown-item
+ v-for="attrItem in attributesList"
+ :key="attrItem.id"
+ :is-check-item="true"
+ :is-checked="isAttributeChecked(attrItem.id)"
+ :data-testid="`${issuableAttribute}-items`"
+ @click="updateAttribute(attrItem.id)"
+ >
+ {{ attrItem.title }}
+ </gl-dropdown-item>
+ </slot>
</template>
</gl-dropdown>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
index db8d67d86dc..2e7b3e149b2 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
@@ -32,14 +32,7 @@ export default {
return {
authors: this.config.initialAuthors || [],
defaultAuthors: this.config.defaultAuthors || [DEFAULT_LABEL_ANY],
- preloadedAuthors: [
- {
- id: gon.current_user_id,
- name: gon.current_user_fullname,
- username: gon.current_username,
- avatar_url: gon.current_user_avatar_url,
- },
- ],
+ preloadedAuthors: this.config.preloadedAuthors || [],
loading: false,
};
},
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
index 6bd67a4cdf0..fb6b9e4bc0d 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
@@ -83,7 +83,10 @@ export default {
return Boolean(this.recentTokenValuesStorageKey);
},
recentTokenIds() {
- return this.recentTokenValues.map((tokenValue) => tokenValue.id || tokenValue.name);
+ return this.recentTokenValues.map((tokenValue) => tokenValue[this.valueIdentifier]);
+ },
+ preloadedTokenIds() {
+ return this.preloadedTokenValues.map((tokenValue) => tokenValue[this.valueIdentifier]);
},
currentTokenValue() {
if (this.fnCurrentTokenValue) {
@@ -103,7 +106,9 @@ export default {
return this.searchKey
? this.tokenValues
: this.tokenValues.filter(
- (tokenValue) => !this.recentTokenIds.includes(tokenValue[this.valueIdentifier]),
+ (tokenValue) =>
+ !this.recentTokenIds.includes(tokenValue[this.valueIdentifier]) &&
+ !this.preloadedTokenIds.includes(tokenValue[this.valueIdentifier]),
);
},
},
@@ -125,7 +130,15 @@ export default {
}, DEBOUNCE_DELAY);
},
handleTokenValueSelected(activeTokenValue) {
- if (this.isRecentTokenValuesEnabled && activeTokenValue) {
+ // Make sure that;
+ // 1. Recently used values feature is enabled
+ // 2. User has actually selected a value
+ // 3. Selected value is not part of preloaded list.
+ if (
+ this.isRecentTokenValuesEnabled &&
+ activeTokenValue &&
+ !this.preloadedTokenIds.includes(activeTokenValue[this.valueIdentifier])
+ ) {
setTokenValueToRecentlyUsed(this.recentTokenValuesStorageKey, activeTokenValue);
}
},
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index a6235d8fc04..3d8cdd766bf 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -10,6 +10,7 @@ class Groups::BoardsController < Groups::ApplicationController
push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false)
push_frontend_feature_flag(:board_multi_select, group, default_enabled: :yaml)
push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
+ push_frontend_feature_flag(:iteration_cadences, group, default_enabled: :yaml)
end
feature_category :boards
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index a755d242d4a..cf6d34b2042 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -33,6 +33,7 @@ class GroupsController < Groups::ApplicationController
before_action do
push_frontend_feature_flag(:vue_issuables_list, @group)
+ push_frontend_feature_flag(:iteration_cadences, @group, default_enabled: :yaml)
end
before_action :export_rate_limit, only: [:export, :download_export]
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index ee8bacbac61..43c9046f850 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -10,6 +10,7 @@ class Projects::BoardsController < Projects::ApplicationController
push_frontend_feature_flag(:swimlanes_buffered_rendering, project, default_enabled: :yaml)
push_frontend_feature_flag(:graphql_board_lists, project, default_enabled: :yaml)
push_frontend_feature_flag(:board_multi_select, project, default_enabled: :yaml)
+ push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
end
feature_category :boards
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 848463bc3ec..295213bd38c 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -46,6 +46,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:usage_data_design_action, project, default_enabled: true)
push_frontend_feature_flag(:improved_emoji_picker, project, default_enabled: :yaml)
push_frontend_feature_flag(:vue_issues_list, project)
+ push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
end
before_action only: :show do
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index 4af7508b935..006cb8a2201 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -10,6 +10,10 @@ 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(:ci_daily_limit_for_pipeline_schedules, @project, default_enabled: :yaml)
+ end
+
feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/graphql/mutations/ci/runners_registration_token/reset.rb b/app/graphql/mutations/ci/runners_registration_token/reset.rb
new file mode 100644
index 00000000000..e1cdd9a22a5
--- /dev/null
+++ b/app/graphql/mutations/ci/runners_registration_token/reset.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Ci
+ module RunnersRegistrationToken
+ class Reset < BaseMutation
+ graphql_name 'RunnersRegistrationTokenReset'
+
+ authorize :update_runners_registration_token
+
+ ScopeID = ::GraphQL::ID_TYPE
+
+ argument :type, ::Types::Ci::RunnerTypeEnum,
+ required: true,
+ description: 'Scope of the object to reset the token for.'
+
+ argument :id, ScopeID,
+ required: false,
+ description: 'ID of the project or group to reset the token for. Omit if resetting instance runner token.'
+
+ field :token,
+ GraphQL::STRING_TYPE,
+ null: true,
+ description: 'The runner token after mutation.'
+
+ def resolve(**args)
+ {
+ token: reset_token(**args),
+ errors: []
+ }
+ end
+
+ private
+
+ def find_object(type:, **args)
+ id = args[:id]
+
+ case type
+ when 'group_type'
+ GitlabSchema.object_from_id(id, expected_type: ::Group)
+ when 'project_type'
+ GitlabSchema.object_from_id(id, expected_type: ::Project)
+ end
+ end
+
+ def reset_token(type:, **args)
+ id = args[:id]
+
+ case type
+ when 'instance_type'
+ raise Gitlab::Graphql::Errors::ArgumentError, "id must not be specified for '#{type}' scope" if id.present?
+
+ authorize!(:global)
+
+ ApplicationSetting.current.reset_runners_registration_token!
+ ApplicationSetting.current_without_cache.runners_registration_token
+ when 'group_type', 'project_type'
+ project_or_group = authorized_find!(type: type, id: id)
+ project_or_group.reset_runners_token!
+ project_or_group.runners_token
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 561cbf24da3..6b1146f8f09 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -101,6 +101,7 @@ module Types
mount_mutation Mutations::Ci::Job::Retry
mount_mutation Mutations::Ci::Runner::Update, feature_flag: :runner_graphql_query
mount_mutation Mutations::Ci::Runner::Delete, feature_flag: :runner_graphql_query
+ mount_mutation Mutations::Ci::RunnersRegistrationToken::Reset, feature_flag: :runner_graphql_query
mount_mutation Mutations::Namespace::PackageSettings::Update
mount_mutation Mutations::UserCallouts::Create
end
diff --git a/app/helpers/invite_members_helper.rb b/app/helpers/invite_members_helper.rb
index 8e0511e40f2..3c290701a5f 100644
--- a/app/helpers/invite_members_helper.rb
+++ b/app/helpers/invite_members_helper.rb
@@ -31,4 +31,12 @@ module InviteMembersHelper
{ member_human_access: member.human_access, name: member.source.name }
end
end
+
+ def group_select_data(group)
+ if group.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy
+ { groups_filter: 'descendant_groups', parent_id: group.root_ancestor.id }
+ else
+ {}
+ end
+ end
end
diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb
index b511a928a2c..b8ddb932b73 100644
--- a/app/helpers/nav/top_nav_helper.rb
+++ b/app/helpers/nav/top_nav_helper.rb
@@ -276,7 +276,11 @@ module Nav
builder = ::Gitlab::Nav::TopNavMenuBuilder.new
builder.add_primary_menu_item(id: 'your', title: _('Your groups'), href: dashboard_groups_path)
builder.add_primary_menu_item(id: 'explore', title: _('Explore groups'), href: explore_groups_path)
- builder.add_secondary_menu_item(id: 'create', title: _('Create group'), href: new_group_path)
+
+ if current_user.can_create_group?
+ builder.add_secondary_menu_item(id: 'create', title: _('Create group'), href: new_group_path)
+ end
+
builder.build
end
end
diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb
index 04465f7798c..fe41c041b4f 100644
--- a/app/helpers/packages_helper.rb
+++ b/app/helpers/packages_helper.rb
@@ -53,4 +53,14 @@ module PackagesHelper
category = args.delete(:category) || self.class.name
::Gitlab::Tracking.event(category, event_name.to_s, **args)
end
+
+ def show_cleanup_policy_on_alert(project)
+ Gitlab.com? &&
+ Gitlab.config.registry.enabled &&
+ project.container_registry_enabled &&
+ !Gitlab::CurrentSettings.container_expiration_policies_enable_historic_entries &&
+ Feature.enabled?(:container_expiration_policies_historic_entry, project) &&
+ project.container_expiration_policy.nil? &&
+ project.container_repositories.exists?
+ end
end
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index 5248a80f710..586142a7646 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -18,7 +18,6 @@ module Ci
ACCESSIBILITY_REPORT_FILE_TYPES = %w[accessibility].freeze
NON_ERASABLE_FILE_TYPES = %w[trace].freeze
TERRAFORM_REPORT_FILE_TYPES = %w[terraform].freeze
- UNSUPPORTED_FILE_TYPES = %i[license_management].freeze
SAST_REPORT_TYPES = %w[sast].freeze
SECRET_DETECTION_REPORT_TYPES = %w[secret_detection].freeze
DEFAULT_FILE_NAMES = {
@@ -35,7 +34,6 @@ module Ci
dependency_scanning: 'gl-dependency-scanning-report.json',
container_scanning: 'gl-container-scanning-report.json',
dast: 'gl-dast-report.json',
- license_management: 'gl-license-management-report.json',
license_scanning: 'gl-license-scanning-report.json',
performance: 'performance.json',
browser_performance: 'browser-performance.json',
@@ -74,7 +72,6 @@ module Ci
dependency_scanning: :raw,
container_scanning: :raw,
dast: :raw,
- license_management: :raw,
license_scanning: :raw,
# All these file formats use `raw` as we need to store them uncompressed
@@ -102,7 +99,6 @@ module Ci
dependency_scanning
dotenv
junit
- license_management
license_scanning
lsif
metrics
@@ -124,7 +120,6 @@ module Ci
mount_file_store_uploader JobArtifactUploader
validates :file_format, presence: true, unless: :trace?, on: :create
- validate :validate_supported_file_format!, on: :create
validate :validate_file_format!, unless: :trace?, on: :create
before_save :set_size, if: :file_changed?
@@ -199,8 +194,7 @@ module Ci
container_scanning: 7, ## EE-specific
dast: 8, ## EE-specific
codequality: 9, ## EE-specific
- license_management: 10, ## EE-specific
- license_scanning: 101, ## EE-specific till 13.0
+ license_scanning: 101, ## EE-specific
performance: 11, ## EE-specific till 13.2
metrics: 12, ## EE-specific
metrics_referee: 13, ## runner referees
@@ -233,14 +227,6 @@ module Ci
hashed_path: 2
}
- def validate_supported_file_format!
- return if Feature.disabled?(:drop_license_management_artifact, project, default_enabled: true)
-
- if UNSUPPORTED_FILE_TYPES.include?(self.file_type&.to_sym)
- errors.add(:base, _("File format is no longer supported"))
- end
- end
-
def validate_file_format!
unless TYPE_AND_FORMAT_PAIRS[self.file_type&.to_sym] == self.file_format&.to_sym
errors.add(:base, _('Invalid file format with specified file type'))
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index b405be8620c..effe2d95a99 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -63,6 +63,10 @@ module Ci
.execute(self, fallback_method: method(:calculate_next_run_at))
end
+ def daily_limit
+ project.actual_limits.limit_for(:ci_daily_pipeline_schedule_triggers)
+ end
+
private
def worker_cron_expression
diff --git a/app/models/project.rb b/app/models/project.rb
index f7eba76849d..3a89a85d65d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -421,12 +421,12 @@ class Project < ApplicationRecord
delegate :last_pipeline, to: :commit, allow_nil: true
delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true
delegate :dashboard_timezone, to: :metrics_setting, allow_nil: true, prefix: true
- delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci
- delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings, prefix: :ci
+ delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
+ delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :job_token_scope_enabled, :job_token_scope_enabled=, :job_token_scope_enabled?, to: :ci_cd_settings, prefix: :ci
- delegate :keep_latest_artifact, :keep_latest_artifact=, :keep_latest_artifact?, :keep_latest_artifacts_available?, to: :ci_cd_settings
+ delegate :keep_latest_artifact, :keep_latest_artifact=, :keep_latest_artifact?, :keep_latest_artifacts_available?, to: :ci_cd_settings, allow_nil: true
delegate :restrict_user_defined_variables, :restrict_user_defined_variables=, :restrict_user_defined_variables?,
- to: :ci_cd_settings
+ to: :ci_cd_settings, allow_nil: true
delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true
delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?,
:allow_merge_on_skipped_pipeline=, :has_confluence?, :allow_editing_commit_messages?,
diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb
index 48386a84e39..2e8ff1b7b49 100644
--- a/app/models/user_callout.rb
+++ b/app/models/user_callout.rb
@@ -30,7 +30,8 @@ class UserCallout < ApplicationRecord
eoa_bronze_plan_banner: 28, # EE-only
pipeline_needs_banner: 29,
pipeline_needs_hover_tip: 30,
- web_ide_ci_environments_guidance: 31
+ web_ide_ci_environments_guidance: 31,
+ security_configuration_upgrade_banner: 32
}
validates :user, presence: true
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 73757891cd6..35d38bac7fa 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -115,6 +115,7 @@ class GlobalPolicy < BasePolicy
enable :approve_user
enable :reject_user
enable :read_usage_trends_measurement
+ enable :update_runners_registration_token
end
# We can't use `read_statistics` because the user may have different permissions for different projects
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index dc8ecfa4333..41c5757854e 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -144,6 +144,7 @@ class GroupPolicy < BasePolicy
enable :admin_cluster
enable :read_deploy_token
enable :create_jira_connect_subscription
+ enable :update_runners_registration_token
end
rule { owner }.policy do
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 4dfdbd87a34..e93c60c3710 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -419,6 +419,7 @@ class ProjectPolicy < BasePolicy
enable :update_freeze_period
enable :destroy_freeze_period
enable :admin_feature_flags_client
+ enable :update_runners_registration_token
end
rule { public_project & metrics_dashboard_allowed }.policy do
diff --git a/app/services/ci/pipeline_schedules/calculate_next_run_service.rb b/app/services/ci/pipeline_schedules/calculate_next_run_service.rb
index 4a9ca5d3045..9978b2d4775 100644
--- a/app/services/ci/pipeline_schedules/calculate_next_run_service.rb
+++ b/app/services/ci/pipeline_schedules/calculate_next_run_service.rb
@@ -41,11 +41,11 @@ module Ci
def plan_cron
strong_memoize(:plan_cron) do
- daily_scheduled_pipeline_limit = project.actual_limits.limit_for(:ci_daily_pipeline_schedule_triggers)
+ daily_limit = @schedule.daily_limit
- next unless daily_scheduled_pipeline_limit
+ next unless daily_limit
- every_x_minutes = (1.day.in_minutes / daily_scheduled_pipeline_limit).to_i
+ every_x_minutes = (1.day.in_minutes / daily_limit).to_i
Gitlab::Ci::CronParser.parse_natural("every #{every_x_minutes} minutes", Time.zone.name)
end
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index f258aa13376..77d2139b3d1 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -91,12 +91,10 @@ class WebHookService
end
def async_execute
- if rate_limited?(hook)
- log_rate_limit(hook)
- else
- Gitlab::ApplicationContext.with_context(hook.application_context) do
- WebHookWorker.perform_async(hook.id, data, hook_name)
- end
+ Gitlab::ApplicationContext.with_context(hook.application_context) do
+ break log_rate_limit if rate_limited?
+
+ WebHookWorker.perform_async(hook.id, data, hook_name)
end
end
@@ -177,7 +175,7 @@ class WebHookService
response.body.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
end
- def rate_limited?(hook)
+ def rate_limited?
return false unless Feature.enabled?(:web_hooks_rate_limit, default_enabled: :yaml)
return false if rate_limit.nil?
@@ -192,18 +190,13 @@ class WebHookService
@rate_limit ||= hook.rate_limit
end
- def log_rate_limit(hook)
- payload = {
+ def log_rate_limit
+ Gitlab::AuthLogger.error(
message: 'Webhook rate limit exceeded',
hook_id: hook.id,
hook_type: hook.type,
- hook_name: hook_name
- }
-
- Gitlab::AuthLogger.error(payload)
-
- # Also log into application log for now, so we can use this information
- # to determine suitable limits for gitlab.com
- Gitlab::AppLogger.error(payload)
+ hook_name: hook_name,
+ **Gitlab::ApplicationContext.current
+ )
end
end
diff --git a/app/views/admin/application_settings/_diff_limits.html.haml b/app/views/admin/application_settings/_diff_limits.html.haml
index ff22f6181b3..5351ac5abd1 100644
--- a/app/views/admin/application_settings/_diff_limits.html.haml
+++ b/app/views/admin/application_settings/_diff_limits.html.haml
@@ -3,10 +3,10 @@
%fieldset
.form-group
- = f.label :diff_max_patch_bytes, 'Maximum diff patch size in bytes', class: 'label-light'
+ = f.label :diff_max_patch_bytes, _('Maximum diff patch size in bytes'), class: 'label-light'
= f.number_field :diff_max_patch_bytes, class: 'form-control gl-form-input'
%span.form-text.text-muted
- Collapse diffs larger than this size, and show a 'too large' message instead.
+ = _("Collapse diffs larger than this size, and show a 'too large' message instead.")
= link_to sprite_icon('question-o'),
help_page_path('user/admin_area/diff_limits')
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index fe5759ecdbf..b68c22b6942 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -1,10 +1,12 @@
.broadcast-message.broadcast-banner-message.gl-alert-warning.js-broadcast-banner-message-preview.gl-mt-3{ style: broadcast_message_style(@broadcast_message), class: ('gl-display-none' unless @broadcast_message.banner? ) }
- = sprite_icon('bullhorn', css_class: 'vertical-align-text-top')
- .js-broadcast-message-preview
- - if @broadcast_message.message.present?
- = render_broadcast_message(@broadcast_message)
- - else
- = _('Your message here')
+ .gl-alert-container
+ = sprite_icon('bullhorn', css_class: 'vertical-align-text-top')
+ .js-broadcast-message-preview
+ .gl-alert-content
+ - if @broadcast_message.message.present?
+ = render_broadcast_message(@broadcast_message)
+ - else
+ = _('Your message here')
.d-flex.justify-content-center
.broadcast-message.broadcast-notification-message.preview.js-broadcast-notification-message-preview.mt-2{ class: ('hidden' unless @broadcast_message.notification? ) }
= sprite_icon('bullhorn', css_class: 'vertical-align-text-top')
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 84a9b988d22..e7e0e58f6fb 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -27,10 +27,12 @@
- if @group.new_record?
.form-group.row
.offset-sm-2.col-sm-10
- .gl-alert.gl-alert-info
- = sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
- .gl-alert-body
- = render 'shared/group_tips'
+ .gl-alert.gl-alert-
+ .gl-alert-container
+ = sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-content
+ .gl-alert-body
+ = render 'shared/group_tips'
.form-actions
= f.submit _('Create group'), class: "gl-button btn btn-confirm"
= link_to _('Cancel'), admin_groups_path, class: "gl-button btn btn-default btn-cancel"
diff --git a/app/views/groups/_invite_members_modal.html.haml b/app/views/groups/_invite_members_modal.html.haml
index 69ed94e99cc..f4f3c8ce8f7 100644
--- a/app/views/groups/_invite_members_modal.html.haml
+++ b/app/views/groups/_invite_members_modal.html.haml
@@ -4,4 +4,4 @@
is_project: 'false',
access_levels: GroupMember.access_level_roles.to_json,
default_access_level: Gitlab::Access::GUEST,
- help_link: help_page_url('user/permissions') } }
+ help_link: help_page_url('user/permissions') }.merge(group_select_data(group)) }
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 2305bbd96f3..c5b8c5e25a3 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,6 +1,6 @@
- add_page_specific_style 'page_bundles/members'
- page_title _('Group members')
-- groups_select_tag_data = @group.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy ? { groups_filter: 'descendant_groups', parent_id: @group.root_ancestor.id, skip_groups: @skip_groups } : { skip_groups: @skip_groups }
+- groups_select_tag_data = group_select_data(@group).merge({ skip_groups: @skip_groups })
.js-remove-member-modal
.row.gl-mt-3
diff --git a/app/views/layouts/nav/groups_dropdown/_show.html.haml b/app/views/layouts/nav/groups_dropdown/_show.html.haml
index 1ace402fbed..d7b0c7150d4 100644
--- a/app/views/layouts/nav/groups_dropdown/_show.html.haml
+++ b/app/views/layouts/nav/groups_dropdown/_show.html.haml
@@ -12,11 +12,12 @@
= nav_link(path: 'groups#explore') do
= link_to explore_groups_path, data: { track_label: "groups_dropdown_explore_groups", track_event: "click_link" } do
= _('Explore groups')
- = nav_link(path: 'groups/new#create-group-pane', html_options: { class: 'gl-border-0 gl-border-t-1 gl-border-solid gl-border-gray-100' }) do
- = link_to new_group_path(anchor: 'create-group-pane'), data: { track_label: "groups_dropdown_create_group", track_event: "click_link" } do
- = _('Create group')
- = nav_link(path: 'groups/new#import-group-pane') do
- = link_to new_group_path(anchor: 'import-group-pane'), data: { track_label: "groups_dropdown_import_group", track_event: "click_link" } do
- = _('Import group')
+ - if current_user.can_create_group?
+ = nav_link(path: 'groups/new#create-group-pane', html_options: { class: 'gl-border-0 gl-border-t-1 gl-border-solid gl-border-gray-100' }) do
+ = link_to new_group_path(anchor: 'create-group-pane'), data: { track_label: "groups_dropdown_create_group", track_event: "click_link" } do
+ = _('Create group')
+ = nav_link(path: 'groups/new#import-group-pane') do
+ = link_to new_group_path(anchor: 'import-group-pane'), data: { track_label: "groups_dropdown_import_group", track_event: "click_link" } do
+ = _('Import group')
.frequent-items-dropdown-content
#js-groups-dropdown{ data: { user_name: current_user.username, group: group_meta } }
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index e6c9a7166a9..9e0dd93c683 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -35,8 +35,10 @@
- if hidden > 0
%li.gl-alert.gl-alert-warning
- = sprite_icon('warning', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
- = n_('%s additional commit has been omitted to prevent performance issues.', '%s additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden)
+ .gl-alert-container
+ = sprite_icon('warning', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-content
+ = n_('%s additional commit has been omitted to prevent performance issues.', '%s additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden)
- if project.context_commits_enabled? && can_update_merge_request && context_commits&.empty?
%button.gl-button.btn.btn-default.mt-3.add-review-item-modal-trigger{ type: "button", data: { context_commits_empty: 'true' } }
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index 628c4780cf2..66aee7dedf3 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -7,7 +7,7 @@
.form-group.row
.col-md-9
= f.label :cron, _('Interval Pattern'), class: 'label-bold'
- #interval-pattern-input{ data: { initial_interval: @schedule.cron } }
+ #interval-pattern-input{ data: { initial_interval: @schedule.cron, daily_limit: @schedule.daily_limit } }
.form-group.row
.col-md-9
= f.label :cron_timezone, _('Cron Timezone'), class: 'label-bold'
diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml
index f56fd7f557d..bdb5f021b70 100644
--- a/app/views/projects/registry/repositories/index.html.haml
+++ b/app/views/projects/registry/repositories/index.html.haml
@@ -18,6 +18,8 @@
"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,
+ "cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project),
character_error: @character_error.to_s,
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index d955dabd04c..ade3d40a8df 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -76,17 +76,7 @@
= render 'projects/triggers/index'
- if settings_container_registry_expiration_policy_available?(@project)
- %section.settings.no-animate#js-registry-policies{ class: ('expanded' if expanded) }
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
- = _("Clean up image tags")
- %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _("Save space and find images in the Container Registry. Remove unneeded tags and keep only the ones you want.")
- = link_to _('How does cleanup work?'), help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy'), target: '_blank', rel: 'noopener noreferrer'
- .settings-content
- = render 'projects/registry/settings/index'
+ = render 'projects/registry/settings/index'
= render_if_exists 'projects/settings/ci_cd/auto_rollback', expanded: expanded
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 561ac7b347d..626ddc20431 100644
--- a/app/views/projects/settings/packages_and_registries/show.html.haml
+++ b/app/views/projects/settings/packages_and_registries/show.html.haml
@@ -1,16 +1,15 @@
- breadcrumb_title _('Packages & Registries')
- page_title _('Packages & Registries')
- @content_class = 'limit-container-width' unless fluid_layout
-- expanded = true
-%section.settings.no-animate#js-registry-policies{ class: ('expanded' if expanded) }
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
- = _("Clean up image tags")
- %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p
- = _("Save space and find images in the Container Registry. Remove unneeded tags and keep only the ones you want.")
- = link_to _('How does cleanup work?'), help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy'), target: '_blank', rel: 'noopener noreferrer'
- .settings-content
- = render 'projects/registry/settings/index'
+#js-registry-settings{ data: { project_id: @project.id,
+ project_path: @project.full_path,
+ cadence_options: cadence_options.to_json,
+ keep_n_options: keep_n_options.to_json,
+ older_than_options: older_than_options.to_json,
+ is_admin: current_user&.admin.to_s,
+ admin_settings_path: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'),
+ enable_historic_entries: container_expiration_policies_historic_entry_enabled?(@project).to_s,
+ help_page_path: help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy'),
+ show_cleanup_policy_on_alert: show_cleanup_policy_on_alert(@project).to_s,
+ tags_regex_help_page_path: help_page_path('user/packages/container_registry/index', anchor: 'regex-pattern-examples') } }
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 59e6cd43325..109b08bf0ec 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -1428,7 +1428,7 @@
:urgency: :high
:resource_boundary: :cpu
:weight: 3
- :idempotent: true
+ :idempotent:
:tags: []
- :name: pipeline_creation:create_pipeline
:worker_name: CreatePipelineWorker
@@ -1610,7 +1610,7 @@
:urgency: :high
:resource_boundary: :unknown
:weight: 5
- :idempotent:
+ :idempotent: true
:tags: []
- :name: pipeline_processing:stage_update
:worker_name: StageUpdateWorker
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index 3c48c4ba3cd..9702fac39ba 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# rubocop: disable Scalability/IdempotentWorker
class ExpirePipelineCacheWorker
include ApplicationWorker
@@ -9,8 +10,12 @@ class ExpirePipelineCacheWorker
queue_namespace :pipeline_cache
urgency :high
worker_resource_boundary :cpu
+ data_consistency :delayed, feature_flag: :load_balancing_for_expire_pipeline_cache_worker
- idempotent!
+ # This worker _should_ be idempotent, but due to us moving this to data_consistency :delayed
+ # and an ongoing incompatibility between the two switches, we need to disable this.
+ # Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/325291 is resolved
+ # idempotent!
# rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
@@ -21,3 +26,4 @@ class ExpirePipelineCacheWorker
end
# rubocop: enable CodeReuse/ActiveRecord
end
+# rubocop:enable Scalability/IdempotentWorker
diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb
index 43aaac4e311..a35b32c35f2 100644
--- a/app/workers/pipeline_process_worker.rb
+++ b/app/workers/pipeline_process_worker.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class PipelineProcessWorker # rubocop:disable Scalability/IdempotentWorker
+class PipelineProcessWorker
include ApplicationWorker
sidekiq_options retry: 3
@@ -10,7 +10,9 @@ class PipelineProcessWorker # rubocop:disable Scalability/IdempotentWorker
feature_category :continuous_integration
urgency :high
loggable_arguments 1
- data_consistency :delayed, feature_flag: :load_balancing_for_pipeline_process_worker
+
+ idempotent!
+ deduplicate :until_executing, feature_flag: :ci_idempotent_pipeline_process_worker
# rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)