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:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-03 00:09:44 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-03 00:09:44 +0300
commitf96f2720d1b21b76eadedc54fdea67cb70e98d94 (patch)
tree527d27d5ceb816969e315b6223b3ddb2ca128dae /app
parentad05e1db038a2e983d25555144fa29063e060c50 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/diffs/components/app.vue30
-rw-r--r--app/assets/javascripts/diffs/components/settings_dropdown.vue34
-rw-r--r--app/assets/javascripts/diffs/constants.js6
-rw-r--r--app/assets/javascripts/diffs/i18n.js4
-rw-r--r--app/assets/javascripts/diffs/index.js2
-rw-r--r--app/assets/javascripts/diffs/store/actions.js16
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js3
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js1
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js5
-rw-r--r--app/assets/javascripts/diffs/utils/preferences.js22
-rw-r--r--app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue111
-rw-r--r--app/assets/javascripts/pipeline_new/constants.js2
-rw-r--r--app/assets/javascripts/pipeline_new/index.js12
-rw-r--r--app/assets/javascripts/pipeline_new/utils/format_refs.js18
-rw-r--r--app/assets/javascripts/registry/settings/components/expiration_textarea.vue3
-rw-r--r--app/assets/javascripts/registry/settings/components/registry_settings_app.vue5
-rw-r--r--app/assets/javascripts/registry/settings/components/settings_form.vue233
-rw-r--r--app/assets/javascripts/registry/settings/constants.js14
-rw-r--r--app/assets/javascripts/registry/shared/utils.js7
-rw-r--r--app/controllers/concerns/snippets_actions.rb2
-rw-r--r--app/models/concerns/has_repository.rb2
-rw-r--r--app/models/custom_emoji.rb2
-rw-r--r--app/models/terraform/state.rb6
-rw-r--r--app/views/projects/pipelines/new.html.haml4
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml4
25 files changed, 423 insertions, 125 deletions
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 9218cc00303..53eb8cd8eb8 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -10,7 +10,10 @@ import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { isSingleViewStyle } from '~/helpers/diffs_helper';
import { updateHistory } from '~/lib/utils/url_utility';
-import eventHub from '../../notes/event_hub';
+
+import notesEventHub from '../../notes/event_hub';
+import eventHub from '../event_hub';
+
import CompareVersions from './compare_versions.vue';
import DiffFile from './diff_file.vue';
import NoChanges from './no_changes.vue';
@@ -22,6 +25,7 @@ import MergeConflictWarning from './merge_conflict_warning.vue';
import CollapsedFilesWarning from './collapsed_files_warning.vue';
import { diffsApp } from '../utils/performance';
+import { fileByFile } from '../utils/preferences';
import {
TREE_LIST_WIDTH_STORAGE_KEY,
@@ -34,6 +38,7 @@ import {
ALERT_OVERFLOW_HIDDEN,
ALERT_MERGE_CONFLICT,
ALERT_COLLAPSED_FILES,
+ EVT_VIEW_FILE_BY_FILE,
} from '../constants';
export default {
@@ -114,7 +119,7 @@ export default {
required: false,
default: false,
},
- viewDiffsFileByFile: {
+ fileByFileUserPreference: {
type: Boolean,
required: false,
default: false,
@@ -154,6 +159,7 @@ export default {
'conflictResolutionPath',
'canMerge',
'hasConflicts',
+ 'viewDiffsFileByFile',
]),
...mapGetters('diffs', ['whichCollapsedTypes', 'isParallelView', 'currentDiffIndex']),
...mapGetters(['isNotesFetched', 'getNoteableData']),
@@ -254,7 +260,7 @@ export default {
projectPath: this.projectPath,
dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover,
- viewDiffsFileByFile: this.viewDiffsFileByFile,
+ viewDiffsFileByFile: fileByFile(this.fileByFileUserPreference),
});
if (this.shouldShow) {
@@ -278,8 +284,10 @@ export default {
created() {
this.adjustView();
- eventHub.$once('fetchDiffData', this.fetchData);
- eventHub.$on('refetchDiffData', this.refetchDiffData);
+ notesEventHub.$once('fetchDiffData', this.fetchData);
+ notesEventHub.$on('refetchDiffData', this.refetchDiffData);
+ eventHub.$on(EVT_VIEW_FILE_BY_FILE, this.fileByFileListener);
+
this.CENTERED_LIMITED_CONTAINER_CLASSES = CENTERED_LIMITED_CONTAINER_CLASSES;
this.unwatchDiscussions = this.$watch(
@@ -300,8 +308,10 @@ export default {
beforeDestroy() {
diffsApp.deinstrument();
- eventHub.$off('fetchDiffData', this.fetchData);
- eventHub.$off('refetchDiffData', this.refetchDiffData);
+ eventHub.$off(EVT_VIEW_FILE_BY_FILE, this.fileByFileListener);
+ notesEventHub.$off('refetchDiffData', this.refetchDiffData);
+ notesEventHub.$off('fetchDiffData', this.fetchData);
+
this.removeEventListeners();
},
methods: {
@@ -319,7 +329,11 @@ export default {
'scrollToFile',
'setShowTreeList',
'navigateToDiffFileIndex',
+ 'setFileByFile',
]),
+ fileByFileListener({ setting } = {}) {
+ this.setFileByFile({ fileByFile: setting });
+ },
navigateToDiffFileNumber(number) {
this.navigateToDiffFileIndex(number - 1);
},
@@ -371,7 +385,7 @@ export default {
}
if (!this.isNotesFetched) {
- eventHub.$emit('fetchNotesData');
+ notesEventHub.$emit('fetchNotesData');
}
},
setDiscussions() {
diff --git a/app/assets/javascripts/diffs/components/settings_dropdown.vue b/app/assets/javascripts/diffs/components/settings_dropdown.vue
index 590b2127e6b..b8904de8049 100644
--- a/app/assets/javascripts/diffs/components/settings_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/settings_dropdown.vue
@@ -1,16 +1,38 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
-import { GlButtonGroup, GlButton, GlDropdown } from '@gitlab/ui';
+import { GlButtonGroup, GlButton, GlDropdown, GlFormCheckbox } from '@gitlab/ui';
+
+import eventHub from '../event_hub';
+import { EVT_VIEW_FILE_BY_FILE } from '../constants';
+import { SETTINGS_DROPDOWN } from '../i18n';
export default {
+ i18n: SETTINGS_DROPDOWN,
components: {
GlButtonGroup,
GlButton,
GlDropdown,
+ GlFormCheckbox,
+ },
+ data() {
+ return {
+ checked: false,
+ };
},
computed: {
...mapGetters('diffs', ['isInlineView', 'isParallelView']),
- ...mapState('diffs', ['renderTreeList', 'showWhitespace']),
+ ...mapState('diffs', ['renderTreeList', 'showWhitespace', 'viewDiffsFileByFile']),
+ },
+ watch: {
+ viewDiffsFileByFile() {
+ this.checked = this.viewDiffsFileByFile;
+ },
+ checked() {
+ eventHub.$emit(EVT_VIEW_FILE_BY_FILE, { setting: this.checked });
+ },
+ },
+ created() {
+ this.checked = this.viewDiffsFileByFile;
},
methods: {
...mapActions('diffs', [
@@ -19,6 +41,9 @@ export default {
'setRenderTreeList',
'setShowWhitespace',
]),
+ toggleFileByFile() {
+ eventHub.$emit(EVT_VIEW_FILE_BY_FILE, { setting: !this.viewDiffsFileByFile });
+ },
},
};
</script>
@@ -84,5 +109,10 @@ export default {
{{ __('Show whitespace changes') }}
</label>
</div>
+ <div class="gl-mt-3 gl-px-3">
+ <gl-form-checkbox v-model="checked" data-testid="file-by-file" class="gl-mb-0">
+ {{ $options.i18n.fileByFile }}
+ </gl-form-checkbox>
+ </div>
</gl-dropdown>
</template>
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 79f8c08e389..07e27bd8e47 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -77,6 +77,11 @@ export const ALERT_COLLAPSED_FILES = 'collapsed';
export const DIFF_FILE_AUTOMATIC_COLLAPSE = 'automatic';
export const DIFF_FILE_MANUAL_COLLAPSE = 'manual';
+// Diff view single file mode
+export const DIFF_FILE_BY_FILE_COOKIE_NAME = 'fileViewMode';
+export const DIFF_VIEW_FILE_BY_FILE = 'single';
+export const DIFF_VIEW_ALL_FILES = 'all';
+
// State machine states
export const STATE_IDLING = 'idle';
export const STATE_LOADING = 'loading';
@@ -98,6 +103,7 @@ export const RENAMED_DIFF_TRANSITIONS = {
// MR Diffs known events
export const EVT_EXPAND_ALL_FILES = 'mr:diffs:expandAllFiles';
+export const EVT_VIEW_FILE_BY_FILE = 'mr:diffs:preference:fileByFile';
export const EVT_PERF_MARK_FILE_TREE_START = 'mr:diffs:perf:fileTreeStart';
export const EVT_PERF_MARK_FILE_TREE_END = 'mr:diffs:perf:fileTreeEnd';
export const EVT_PERF_MARK_DIFF_FILES_START = 'mr:diffs:perf:filesStart';
diff --git a/app/assets/javascripts/diffs/i18n.js b/app/assets/javascripts/diffs/i18n.js
index 4ec24d452bf..c4ac99ead91 100644
--- a/app/assets/javascripts/diffs/i18n.js
+++ b/app/assets/javascripts/diffs/i18n.js
@@ -16,3 +16,7 @@ export const DIFF_FILE = {
autoCollapsed: __('Files with large changes are collapsed by default.'),
expand: __('Expand file'),
};
+
+export const SETTINGS_DROPDOWN = {
+ fileByFile: __('Show one file at a time'),
+};
diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js
index 06a138b1e13..587220488be 100644
--- a/app/assets/javascripts/diffs/index.js
+++ b/app/assets/javascripts/diffs/index.js
@@ -116,7 +116,7 @@ export default function initDiffsApp(store) {
isFluidLayout: this.isFluidLayout,
dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover,
- viewDiffsFileByFile: this.viewDiffsFileByFile,
+ fileByFileUserPreference: this.viewDiffsFileByFile,
},
});
},
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 1a0e65bbb3e..51d9fe12102 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -44,6 +44,9 @@ import {
EVT_PERF_MARK_FILE_TREE_START,
EVT_PERF_MARK_FILE_TREE_END,
EVT_PERF_MARK_DIFF_FILES_START,
+ DIFF_VIEW_FILE_BY_FILE,
+ DIFF_VIEW_ALL_FILES,
+ DIFF_FILE_BY_FILE_COOKIE_NAME,
} from '../constants';
import { diffViewerModes } from '~/ide/constants';
import { isCollapsed } from '../diff_file';
@@ -57,6 +60,7 @@ export const setBaseConfig = ({ commit }, options) => {
projectPath,
dismissEndpoint,
showSuggestPopover,
+ viewDiffsFileByFile,
} = options;
commit(types.SET_BASE_CONFIG, {
endpoint,
@@ -66,6 +70,7 @@ export const setBaseConfig = ({ commit }, options) => {
projectPath,
dismissEndpoint,
showSuggestPopover,
+ viewDiffsFileByFile,
});
};
@@ -694,3 +699,14 @@ export const navigateToDiffFileIndex = ({ commit, state }, index) => {
commit(types.VIEW_DIFF_FILE, fileHash);
};
+
+export const setFileByFile = ({ commit }, { fileByFile }) => {
+ const fileViewMode = fileByFile ? DIFF_VIEW_FILE_BY_FILE : DIFF_VIEW_ALL_FILES;
+ commit(types.SET_FILE_BY_FILE, fileByFile);
+
+ Cookies.set(DIFF_FILE_BY_FILE_COOKIE_NAME, fileViewMode);
+
+ historyPushState(
+ mergeUrlParams({ [DIFF_FILE_BY_FILE_COOKIE_NAME]: fileViewMode }, window.location.href),
+ );
+};
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 001d9d9f594..c331e52c887 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -5,6 +5,8 @@ import {
DIFF_VIEW_COOKIE_NAME,
DIFF_WHITESPACE_COOKIE_NAME,
} from '../../constants';
+
+import { fileByFile } from '../../utils/preferences';
import { getDefaultWhitespace } from '../utils';
const viewTypeFromQueryString = getParameterValues('view')[0];
@@ -39,6 +41,7 @@ export default () => ({
highlightedRow: null,
renderTreeList: true,
showWhitespace: getDefaultWhitespace(whiteSpaceFromQueryString, whiteSpaceFromCookie),
+ viewDiffsFileByFile: fileByFile(),
fileFinderVisible: false,
dismissEndpoint: '',
showSuggestPopover: true,
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index ed694419ab1..25184028799 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -28,6 +28,7 @@ export const SET_HIGHLIGHTED_ROW = 'SET_HIGHLIGHTED_ROW';
export const SET_TREE_DATA = 'SET_TREE_DATA';
export const SET_RENDER_TREE_LIST = 'SET_RENDER_TREE_LIST';
export const SET_SHOW_WHITESPACE = 'SET_SHOW_WHITESPACE';
+export const SET_FILE_BY_FILE = 'SET_FILE_BY_FILE';
export const TOGGLE_FILE_FINDER_VISIBLE = 'TOGGLE_FILE_FINDER_VISIBLE';
export const REQUEST_FULL_DIFF = 'REQUEST_FULL_DIFF';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 7b08c5e75e1..69ae3f705e3 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -36,6 +36,7 @@ export default {
projectPath,
dismissEndpoint,
showSuggestPopover,
+ viewDiffsFileByFile,
} = options;
Object.assign(state, {
endpoint,
@@ -45,6 +46,7 @@ export default {
projectPath,
dismissEndpoint,
showSuggestPopover,
+ viewDiffsFileByFile,
});
},
@@ -352,4 +354,7 @@ export default {
[types.SET_SHOW_SUGGEST_POPOVER](state) {
state.showSuggestPopover = false;
},
+ [types.SET_FILE_BY_FILE](state, fileByFile) {
+ state.viewDiffsFileByFile = fileByFile;
+ },
};
diff --git a/app/assets/javascripts/diffs/utils/preferences.js b/app/assets/javascripts/diffs/utils/preferences.js
new file mode 100644
index 00000000000..e440de3350a
--- /dev/null
+++ b/app/assets/javascripts/diffs/utils/preferences.js
@@ -0,0 +1,22 @@
+import Cookies from 'js-cookie';
+import { getParameterValues } from '~/lib/utils/url_utility';
+
+import { DIFF_FILE_BY_FILE_COOKIE_NAME, DIFF_VIEW_FILE_BY_FILE } from '../constants';
+
+export function fileByFile(pref = false) {
+ const search = getParameterValues(DIFF_FILE_BY_FILE_COOKIE_NAME)?.[0];
+ const cookie = Cookies.get(DIFF_FILE_BY_FILE_COOKIE_NAME);
+ let viewFileByFile = pref;
+
+ // use the cookie first, if it exists
+ if (cookie) {
+ viewFileByFile = cookie === DIFF_VIEW_FILE_BY_FILE;
+ }
+
+ // the search parameter of the URL should override, if it exists
+ if (search) {
+ viewFileByFile = search === DIFF_VIEW_FILE_BY_FILE;
+ }
+
+ return viewFileByFile;
+}
diff --git a/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue b/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
index 790460c79f1..f2d68054e80 100644
--- a/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
+++ b/app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue
@@ -12,10 +12,12 @@ import {
GlLink,
GlDropdown,
GlDropdownItem,
+ GlDropdownSectionHeader,
GlSearchBoxByType,
GlSprintf,
GlLoadingIcon,
} from '@gitlab/ui';
+import * as Sentry from '~/sentry/wrapper';
import { s__, __, n__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import { redirectTo } from '~/lib/utils/url_utility';
@@ -46,6 +48,7 @@ export default {
GlLink,
GlDropdown,
GlDropdownItem,
+ GlDropdownSectionHeader,
GlSearchBoxByType,
GlSprintf,
GlLoadingIcon,
@@ -59,11 +62,19 @@ export default {
type: String,
required: true,
},
+ defaultBranch: {
+ type: String,
+ required: true,
+ },
projectId: {
type: String,
required: true,
},
- refs: {
+ branches: {
+ type: Array,
+ required: true,
+ },
+ tags: {
type: Array,
required: true,
},
@@ -94,7 +105,9 @@ export default {
data() {
return {
searchTerm: '',
- refValue: this.refParam,
+ refValue: {
+ shortName: this.refParam,
+ },
form: {},
error: null,
warnings: [],
@@ -104,9 +117,21 @@ export default {
};
},
computed: {
- filteredRefs() {
- const lowerCasedSearchTerm = this.searchTerm.toLowerCase();
- return this.refs.filter(ref => ref.toLowerCase().includes(lowerCasedSearchTerm));
+ lowerCasedSearchTerm() {
+ return this.searchTerm.toLowerCase();
+ },
+ filteredBranches() {
+ return this.branches.filter(branch =>
+ branch.shortName.toLowerCase().includes(this.lowerCasedSearchTerm),
+ );
+ },
+ filteredTags() {
+ return this.tags.filter(tag =>
+ tag.shortName.toLowerCase().includes(this.lowerCasedSearchTerm),
+ );
+ },
+ hasTags() {
+ return this.tags.length > 0;
},
overMaxWarningsLimit() {
return this.totalWarnings > this.maxWarnings;
@@ -120,14 +145,27 @@ export default {
shouldShowWarning() {
return this.warnings.length > 0 && !this.isWarningDismissed;
},
+ refShortName() {
+ return this.refValue.shortName;
+ },
+ refFullName() {
+ return this.refValue.fullName;
+ },
variables() {
- return this.form[this.refValue]?.variables ?? [];
+ return this.form[this.refFullName]?.variables ?? [];
},
descriptions() {
- return this.form[this.refValue]?.descriptions ?? {};
+ return this.form[this.refFullName]?.descriptions ?? {};
},
},
created() {
+ // this is needed until we add support for ref type in url query strings
+ // ensure default branch is called with full ref on load
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/287815
+ if (this.refValue.shortName === this.defaultBranch) {
+ this.refValue.fullName = `refs/heads/${this.refValue.shortName}`;
+ }
+
this.setRefSelected(this.refValue);
},
methods: {
@@ -170,19 +208,19 @@ export default {
setRefSelected(refValue) {
this.refValue = refValue;
- if (!this.form[refValue]) {
- this.fetchConfigVariables(refValue)
+ if (!this.form[this.refFullName]) {
+ this.fetchConfigVariables(this.refFullName || this.refShortName)
.then(({ descriptions, params }) => {
- Vue.set(this.form, refValue, {
+ Vue.set(this.form, this.refFullName, {
variables: [],
descriptions,
});
// Add default variables from yml
- this.setVariableParams(refValue, VARIABLE_TYPE, params);
+ this.setVariableParams(this.refFullName, VARIABLE_TYPE, params);
})
.catch(() => {
- Vue.set(this.form, refValue, {
+ Vue.set(this.form, this.refFullName, {
variables: [],
descriptions: {},
});
@@ -190,20 +228,19 @@ export default {
.finally(() => {
// Add/update variables, e.g. from query string
if (this.variableParams) {
- this.setVariableParams(refValue, VARIABLE_TYPE, this.variableParams);
+ this.setVariableParams(this.refFullName, VARIABLE_TYPE, this.variableParams);
}
if (this.fileParams) {
- this.setVariableParams(refValue, FILE_TYPE, this.fileParams);
+ this.setVariableParams(this.refFullName, FILE_TYPE, this.fileParams);
}
// Adds empty var at the end of the form
- this.addEmptyVariable(refValue);
+ this.addEmptyVariable(this.refFullName);
});
}
},
-
isSelected(ref) {
- return ref === this.refValue;
+ return ref.fullName === this.refValue.fullName;
},
removeVariable(index) {
this.variables.splice(index, 1);
@@ -211,7 +248,6 @@ export default {
canRemove(index) {
return index < this.variables.length - 1;
},
-
fetchConfigVariables(refValue) {
if (!gon?.features?.newPipelineFormPrefilledVars) {
return Promise.resolve({ params: {}, descriptions: {} });
@@ -251,9 +287,11 @@ export default {
return { params, descriptions };
})
- .catch(() => {
+ .catch(error => {
this.isLoading = false;
+ Sentry.captureException(error);
+
return { params: {}, descriptions: {} };
});
},
@@ -268,7 +306,9 @@ export default {
return axios
.post(this.pipelinesPath, {
- ref: this.refValue,
+ // send shortName as fall back for query params
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/287815
+ ref: this.refValue.fullName || this.refShortName,
variables_attributes: filteredVariables,
})
.then(({ data }) => {
@@ -326,20 +366,29 @@ export default {
</details>
</gl-alert>
<gl-form-group :label="s__('Pipeline|Run for')">
- <gl-dropdown :text="refValue" block>
- <gl-search-box-by-type
- v-model.trim="searchTerm"
- :placeholder="__('Search branches and tags')"
- />
+ <gl-dropdown :text="refShortName" block>
+ <gl-search-box-by-type v-model.trim="searchTerm" :placeholder="__('Search refs')" />
+ <gl-dropdown-section-header>{{ __('Branches') }}</gl-dropdown-section-header>
+ <gl-dropdown-item
+ v-for="branch in filteredBranches"
+ :key="branch.fullName"
+ class="gl-font-monospace"
+ is-check-item
+ :is-checked="isSelected(branch)"
+ @click="setRefSelected(branch)"
+ >
+ {{ branch.shortName }}
+ </gl-dropdown-item>
+ <gl-dropdown-section-header v-if="hasTags">{{ __('Tags') }}</gl-dropdown-section-header>
<gl-dropdown-item
- v-for="(ref, index) in filteredRefs"
- :key="index"
+ v-for="tag in filteredTags"
+ :key="tag.fullName"
class="gl-font-monospace"
is-check-item
- :is-checked="isSelected(ref)"
- @click="setRefSelected(ref)"
+ :is-checked="isSelected(tag)"
+ @click="setRefSelected(tag)"
>
- {{ ref }}
+ {{ tag.shortName }}
</gl-dropdown-item>
</gl-dropdown>
@@ -372,7 +421,7 @@ export default {
:placeholder="s__('CiVariables|Input variable key')"
:class="$options.formElementClasses"
data-testid="pipeline-form-ci-variable-key"
- @change="addEmptyVariable(refValue)"
+ @change="addEmptyVariable(refFullName)"
/>
<gl-form-input
v-model="variable.value"
diff --git a/app/assets/javascripts/pipeline_new/constants.js b/app/assets/javascripts/pipeline_new/constants.js
index 3d4a0ee328d..004bbe7daf4 100644
--- a/app/assets/javascripts/pipeline_new/constants.js
+++ b/app/assets/javascripts/pipeline_new/constants.js
@@ -1,3 +1,5 @@
export const VARIABLE_TYPE = 'env_var';
export const FILE_TYPE = 'file';
export const CONFIG_VARIABLES_TIMEOUT = 5000;
+export const BRANCH_REF_TYPE = 'branch';
+export const TAG_REF_TYPE = 'tag';
diff --git a/app/assets/javascripts/pipeline_new/index.js b/app/assets/javascripts/pipeline_new/index.js
index ff4f677654e..0b85184ec90 100644
--- a/app/assets/javascripts/pipeline_new/index.js
+++ b/app/assets/javascripts/pipeline_new/index.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import PipelineNewForm from './components/pipeline_new_form.vue';
+import formatRefs from './utils/format_refs';
export default () => {
const el = document.getElementById('js-new-pipeline');
@@ -7,17 +8,20 @@ export default () => {
projectId,
pipelinesPath,
configVariablesPath,
+ defaultBranch,
refParam,
varParam,
fileParam,
- refNames,
+ branchRefs,
+ tagRefs,
settingsLink,
maxWarnings,
} = el?.dataset;
const variableParams = JSON.parse(varParam);
const fileParams = JSON.parse(fileParam);
- const refs = JSON.parse(refNames);
+ const branches = formatRefs(JSON.parse(branchRefs), 'branch');
+ const tags = formatRefs(JSON.parse(tagRefs), 'tag');
return new Vue({
el,
@@ -27,10 +31,12 @@ export default () => {
projectId,
pipelinesPath,
configVariablesPath,
+ defaultBranch,
refParam,
variableParams,
fileParams,
- refs,
+ branches,
+ tags,
settingsLink,
maxWarnings: Number(maxWarnings),
},
diff --git a/app/assets/javascripts/pipeline_new/utils/format_refs.js b/app/assets/javascripts/pipeline_new/utils/format_refs.js
new file mode 100644
index 00000000000..e217cd25413
--- /dev/null
+++ b/app/assets/javascripts/pipeline_new/utils/format_refs.js
@@ -0,0 +1,18 @@
+import { BRANCH_REF_TYPE, TAG_REF_TYPE } from '../constants';
+
+export default (refs, type) => {
+ let fullName;
+
+ return refs.map(ref => {
+ if (type === BRANCH_REF_TYPE) {
+ fullName = `refs/heads/${ref}`;
+ } else if (type === TAG_REF_TYPE) {
+ fullName = `refs/tags/${ref}`;
+ }
+
+ return {
+ shortName: ref,
+ fullName,
+ };
+ });
+};
diff --git a/app/assets/javascripts/registry/settings/components/expiration_textarea.vue b/app/assets/javascripts/registry/settings/components/expiration_textarea.vue
index 1e1194ebb5c..3f817e37dca 100644
--- a/app/assets/javascripts/registry/settings/components/expiration_textarea.vue
+++ b/app/assets/javascripts/registry/settings/components/expiration_textarea.vue
@@ -36,7 +36,8 @@ export default {
},
placeholder: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
description: {
type: String,
diff --git a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue
index 264d39a406a..e236834d8e1 100644
--- a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue
+++ b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue
@@ -1,6 +1,6 @@
<script>
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
-import { isEqual, get } from 'lodash';
+import { isEqual, get, isEmpty } from 'lodash';
import expirationPolicyQuery from '../graphql/queries/get_expiration_policy.graphql';
import { FETCH_SETTINGS_ERROR_MESSAGE } from '../../shared/constants';
@@ -60,6 +60,9 @@ export default {
return this.isAdmin ? UNAVAILABLE_ADMIN_FEATURE_TEXT : UNAVAILABLE_USER_FEATURE_TEXT;
},
isEdited() {
+ if (isEmpty(this.containerExpirationPolicy) && isEmpty(this.workingCopy)) {
+ return false;
+ }
return !isEqual(this.containerExpirationPolicy, this.workingCopy);
},
},
diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue
index fe4aee6806e..d0a8081e455 100644
--- a/app/assets/javascripts/registry/settings/components/settings_form.vue
+++ b/app/assets/javascripts/registry/settings/components/settings_form.vue
@@ -1,21 +1,43 @@
<script>
-import { GlCard, GlButton } from '@gitlab/ui';
+import { GlCard, GlButton, GlSprintf } from '@gitlab/ui';
import Tracking from '~/tracking';
import {
UPDATE_SETTINGS_ERROR_MESSAGE,
UPDATE_SETTINGS_SUCCESS_MESSAGE,
-} from '../../shared/constants';
-import ExpirationPolicyFields from '../../shared/components/expiration_policy_fields.vue';
-import { SET_CLEANUP_POLICY_BUTTON, CLEANUP_POLICY_CARD_HEADER } from '../constants';
+} from '~/registry/shared/constants';
+import {
+ SET_CLEANUP_POLICY_BUTTON,
+ KEEP_HEADER_TEXT,
+ KEEP_INFO_TEXT,
+ KEEP_N_LABEL,
+ NAME_REGEX_KEEP_LABEL,
+ NAME_REGEX_KEEP_DESCRIPTION,
+ REMOVE_HEADER_TEXT,
+ REMOVE_INFO_TEXT,
+ EXPIRATION_SCHEDULE_LABEL,
+ NAME_REGEX_LABEL,
+ NAME_REGEX_PLACEHOLDER,
+ NAME_REGEX_DESCRIPTION,
+ CADENCE_LABEL,
+ EXPIRATION_POLICY_FOOTER_NOTE,
+} from '~/registry/settings/constants';
import { formOptionsGenerator } from '~/registry/shared/utils';
-import updateContainerExpirationPolicyMutation from '../graphql/mutations/update_container_expiration_policy.graphql';
-import { updateContainerExpirationPolicy } from '../graphql/utils/cache_update';
+import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.graphql';
+import { updateContainerExpirationPolicy } from '~/registry/settings/graphql/utils/cache_update';
+import ExpirationDropdown from './expiration_dropdown.vue';
+import ExpirationTextarea from './expiration_textarea.vue';
+import ExpirationToggle from './expiration_toggle.vue';
+import ExpirationRunText from './expiration_run_text.vue';
export default {
components: {
GlCard,
GlButton,
- ExpirationPolicyFields,
+ GlSprintf,
+ ExpirationDropdown,
+ ExpirationTextarea,
+ ExpirationToggle,
+ ExpirationRunText,
},
mixins: [Tracking.mixin()],
inject: ['projectPath'],
@@ -35,22 +57,31 @@ export default {
default: false,
},
},
- labelsConfig: {
- cols: 3,
- align: 'right',
- },
+
formOptions: formOptionsGenerator(),
i18n: {
- CLEANUP_POLICY_CARD_HEADER,
+ KEEP_HEADER_TEXT,
+ KEEP_INFO_TEXT,
+ KEEP_N_LABEL,
+ NAME_REGEX_KEEP_LABEL,
SET_CLEANUP_POLICY_BUTTON,
+ NAME_REGEX_KEEP_DESCRIPTION,
+ REMOVE_HEADER_TEXT,
+ REMOVE_INFO_TEXT,
+ EXPIRATION_SCHEDULE_LABEL,
+ NAME_REGEX_LABEL,
+ NAME_REGEX_PLACEHOLDER,
+ NAME_REGEX_DESCRIPTION,
+ CADENCE_LABEL,
+ EXPIRATION_POLICY_FOOTER_NOTE,
},
data() {
return {
tracking: {
label: 'docker_container_retention_and_expiration_policies',
},
- fieldsAreValid: true,
- apiErrors: null,
+ apiErrors: {},
+ localErrors: {},
mutationLoading: false,
};
},
@@ -66,12 +97,18 @@ export default {
showLoadingIcon() {
return this.isLoading || this.mutationLoading;
},
+ fieldsAreValid() {
+ return Object.values(this.localErrors).every(error => error);
+ },
isSubmitButtonDisabled() {
return !this.fieldsAreValid || this.showLoadingIcon;
},
isCancelButtonDisabled() {
return !this.isEdited || this.isLoading || this.mutationLoading;
},
+ isFieldDisabled() {
+ return this.showLoadingIcon || !this.value.enabled;
+ },
mutationVariables() {
return {
projectPath: this.projectPath,
@@ -90,7 +127,8 @@ export default {
},
reset() {
this.track('reset_form');
- this.apiErrors = null;
+ this.apiErrors = {};
+ this.localErrors = {};
this.$emit('reset');
},
setApiErrors(response) {
@@ -101,9 +139,15 @@ export default {
return acc;
}, {});
},
+ setLocalErrors(state, model) {
+ this.localErrors = {
+ ...this.localErrors,
+ [model]: state,
+ };
+ },
submit() {
this.track('submit_form');
- this.apiErrors = null;
+ this.apiErrors = {};
this.mutationLoading = true;
return this.$apollo
.mutate({
@@ -129,11 +173,9 @@ export default {
this.mutationLoading = false;
});
},
- onModelChange(changePayload) {
- this.$emit('input', changePayload.newValue);
- if (this.apiErrors) {
- this.apiErrors[changePayload.modified] = undefined;
- }
+ onModelChange(newValue, model) {
+ this.$emit('input', { ...this.value, [model]: newValue });
+ this.apiErrors[model] = undefined;
},
},
};
@@ -141,42 +183,129 @@ export default {
<template>
<form ref="form-element" @submit.prevent="submit" @reset.prevent="reset">
- <gl-card>
+ <expiration-toggle
+ :value="prefilledForm.enabled"
+ :disabled="showLoadingIcon"
+ class="gl-mb-0!"
+ data-testid="enable-toggle"
+ @input="onModelChange($event, 'enabled')"
+ />
+
+ <div class="gl-display-flex gl-mt-7">
+ <expiration-dropdown
+ v-model="prefilledForm.cadence"
+ :disabled="isFieldDisabled"
+ :form-options="$options.formOptions.cadence"
+ :label="$options.i18n.CADENCE_LABEL"
+ name="cadence"
+ class="gl-mr-7 gl-mb-0!"
+ data-testid="cadence-dropdown"
+ @input="onModelChange($event, 'cadence')"
+ />
+ <expiration-run-text :value="prefilledForm.nextRunAt" class="gl-mb-0!" />
+ </div>
+ <gl-card class="gl-mt-7">
<template #header>
- {{ $options.i18n.CLEANUP_POLICY_CARD_HEADER }}
+ {{ $options.i18n.KEEP_HEADER_TEXT }}
</template>
<template #default>
- <expiration-policy-fields
- :value="prefilledForm"
- :form-options="$options.formOptions"
- :is-loading="isLoading"
- :api-errors="apiErrors"
- @validated="fieldsAreValid = true"
- @invalidated="fieldsAreValid = false"
- @input="onModelChange"
- />
+ <div>
+ <p>
+ <gl-sprintf :message="$options.i18n.KEEP_INFO_TEXT">
+ <template #strong="{content}">
+ <strong>{{ content }}</strong>
+ </template>
+ <template #secondStrong="{content}">
+ <strong>{{ content }}</strong>
+ </template>
+ </gl-sprintf>
+ </p>
+ <expiration-dropdown
+ v-model="prefilledForm.keepN"
+ :disabled="isFieldDisabled"
+ :form-options="$options.formOptions.keepN"
+ :label="$options.i18n.KEEP_N_LABEL"
+ name="keep-n"
+ data-testid="keep-n-dropdown"
+ @input="onModelChange($event, 'keepN')"
+ />
+ <expiration-textarea
+ v-model="prefilledForm.nameRegexKeep"
+ :error="apiErrors.nameRegexKeep"
+ :disabled="isFieldDisabled"
+ :label="$options.i18n.NAME_REGEX_KEEP_LABEL"
+ :description="$options.i18n.NAME_REGEX_KEEP_DESCRIPTION"
+ name="keep-regex"
+ data-testid="keep-regex-textarea"
+ @input="onModelChange($event, 'nameRegexKeep')"
+ @validation="setLocalErrors($event, 'nameRegexKeep')"
+ />
+ </div>
+ </template>
+ </gl-card>
+ <gl-card class="gl-mt-7">
+ <template #header>
+ {{ $options.i18n.REMOVE_HEADER_TEXT }}
</template>
- <template #footer>
- <gl-button
- ref="cancel-button"
- type="reset"
- class="gl-mr-3 gl-display-block float-right"
- :disabled="isCancelButtonDisabled"
- >
- {{ __('Cancel') }}
- </gl-button>
- <gl-button
- ref="save-button"
- type="submit"
- :disabled="isSubmitButtonDisabled"
- :loading="showLoadingIcon"
- variant="success"
- category="primary"
- class="js-no-auto-disable"
- >
- {{ $options.i18n.SET_CLEANUP_POLICY_BUTTON }}
- </gl-button>
+ <template #default>
+ <div>
+ <p>
+ <gl-sprintf :message="$options.i18n.REMOVE_INFO_TEXT">
+ <template #strong="{content}">
+ <strong>{{ content }}</strong>
+ </template>
+ <template #secondStrong="{content}">
+ <strong>{{ content }}</strong>
+ </template>
+ </gl-sprintf>
+ </p>
+ <expiration-dropdown
+ v-model="prefilledForm.olderThan"
+ :disabled="isFieldDisabled"
+ :form-options="$options.formOptions.olderThan"
+ :label="$options.i18n.EXPIRATION_SCHEDULE_LABEL"
+ name="older-than"
+ data-testid="older-than-dropdown"
+ @input="onModelChange($event, 'olderThan')"
+ />
+ <expiration-textarea
+ v-model="prefilledForm.nameRegex"
+ :error="apiErrors.nameRegex"
+ :disabled="isFieldDisabled"
+ :label="$options.i18n.NAME_REGEX_LABEL"
+ :placeholder="$options.i18n.NAME_REGEX_PLACEHOLDER"
+ :description="$options.i18n.NAME_REGEX_DESCRIPTION"
+ name="remove-regex"
+ data-testid="remove-regex-textarea"
+ @input="onModelChange($event, 'nameRegex')"
+ @validation="setLocalErrors($event, 'nameRegex')"
+ />
+ </div>
</template>
</gl-card>
+ <div class="gl-mt-7 gl-display-flex gl-align-items-center">
+ <gl-button
+ data-testid="save-button"
+ type="submit"
+ :disabled="isSubmitButtonDisabled"
+ :loading="showLoadingIcon"
+ variant="success"
+ category="primary"
+ class="js-no-auto-disable gl-mr-4"
+ >
+ {{ $options.i18n.SET_CLEANUP_POLICY_BUTTON }}
+ </gl-button>
+ <gl-button
+ data-testid="cancel-button"
+ type="reset"
+ :disabled="isCancelButtonDisabled"
+ class="gl-mr-4"
+ >
+ {{ __('Cancel') }}
+ </gl-button>
+ <span class="gl-font-style-italic gl-text-gray-400">{{
+ $options.i18n.EXPIRATION_POLICY_FOOTER_NOTE
+ }}</span>
+ </div>
</form>
</template>
diff --git a/app/assets/javascripts/registry/settings/constants.js b/app/assets/javascripts/registry/settings/constants.js
index bc3ec3104ad..ba5820196ff 100644
--- a/app/assets/javascripts/registry/settings/constants.js
+++ b/app/assets/javascripts/registry/settings/constants.js
@@ -1,7 +1,6 @@
import { s__, __ } from '~/locale';
export const SET_CLEANUP_POLICY_BUTTON = __('Save');
-export const CLEANUP_POLICY_CARD_HEADER = s__('ContainerRegistry|Tag expiration policy');
export const UNAVAILABLE_FEATURE_TITLE = s__(
`ContainerRegistry|Cleanup policy for tags is disabled`,
);
@@ -19,34 +18,33 @@ export const TEXT_AREA_INVALID_FEEDBACK = s__(
export const KEEP_HEADER_TEXT = s__('ContainerRegistry|Keep these tags');
export const KEEP_INFO_TEXT = s__(
- 'ContainerRegistry|Tags that match these rules will always be %{strongStart}kept%{strongEnd}, even if they match a removal rule below. The %{secondStrongStart}latest%{secondStrongEnd} tag will always be kept.',
+ 'ContainerRegistry|Tags that match these rules are %{strongStart}kept%{strongEnd}, even if they match a removal rule below. The %{secondStrongStart}latest%{secondStrongEnd} tag is always kept.',
);
export const KEEP_N_LABEL = s__('ContainerRegistry|Keep the most recent:');
export const NAME_REGEX_KEEP_LABEL = s__('ContainerRegistry|Keep tags matching:');
-export const NAME_REGEX_KEEP_PLACEHOLDER = 'production-v.*';
export const NAME_REGEX_KEEP_DESCRIPTION = s__(
- 'ContainerRegistry|Tags with names matching this regex pattern will be kept. %{linkStart}More information%{linkEnd}',
+ 'ContainerRegistry|Tags with names that match this regex pattern are kept. %{linkStart}More information%{linkEnd}',
);
export const REMOVE_HEADER_TEXT = s__('ContainerRegistry|Remove these tags');
export const REMOVE_INFO_TEXT = s__(
- 'ContainerRegistry|Tags that match these rules will be %{strongStart}removed%{strongEnd}, unless kept by a rule above.',
+ 'ContainerRegistry|Tags that match these rules are %{strongStart}removed%{strongEnd}, unless a rule above says to keep them.',
);
export const EXPIRATION_SCHEDULE_LABEL = s__('ContainerRegistry|Remove tags older than:');
export const NAME_REGEX_LABEL = s__('ContainerRegistry|Remove tags matching:');
export const NAME_REGEX_PLACEHOLDER = '.*';
export const NAME_REGEX_DESCRIPTION = s__(
- 'ContainerRegistry|Tags with names matching this regex pattern will be removed. %{linkStart}More information%{linkEnd}',
+ 'ContainerRegistry|Tags with names that match this regex pattern are removed. %{linkStart}More information%{linkEnd}',
);
export const ENABLED_TEXT = __('Enabled');
export const DISABLED_TEXT = __('Disabled');
export const ENABLE_TOGGLE_DESCRIPTION = s__(
- 'ContainerRegistry|%{toggleStatus} - Tags matching the rules defined below will be automatically scheduled for deletion.',
+ 'ContainerRegistry|%{toggleStatus} - Tags that match the rules on this page are automatically scheduled for deletion.',
);
-export const CADENCE_LABEL = s__('ContainerRegistry|Run cleanup every:');
+export const CADENCE_LABEL = s__('ContainerRegistry|Run cleanup:');
export const NEXT_CLEANUP_LABEL = s__('ContainerRegistry|Next cleanup scheduled to run on:');
export const NOT_SCHEDULED_POLICY_TEXT = s__('ContainerRegistry|Not yet scheduled');
diff --git a/app/assets/javascripts/registry/shared/utils.js b/app/assets/javascripts/registry/shared/utils.js
index bdf1ab9507d..5c8c505f835 100644
--- a/app/assets/javascripts/registry/shared/utils.js
+++ b/app/assets/javascripts/registry/shared/utils.js
@@ -21,12 +21,7 @@ export const mapComputedToEvent = (list, root) => {
return result;
};
-export const olderThanTranslationGenerator = variable =>
- n__(
- '%d day until tags are automatically removed',
- '%d days until tags are automatically removed',
- variable,
- );
+export const olderThanTranslationGenerator = variable => n__('%d day', '%d days', variable);
export const keepNTranslationGenerator = variable =>
n__('%d tag per image name', '%d tags per image name', variable);
diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
index df7a574848f..c93e75b438b 100644
--- a/app/controllers/concerns/snippets_actions.rb
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -15,7 +15,7 @@ module SnippetsActions
skip_before_action :verify_authenticity_token,
if: -> { action_name == 'show' && js_request? }
- track_redis_hll_event :show, name: 'i_snippets_show', feature: :usage_data_i_snippets_show, feature_default_enabled: false
+ track_redis_hll_event :show, name: 'i_snippets_show', feature: :usage_data_i_snippets_show, feature_default_enabled: true
respond_to :html
end
diff --git a/app/models/concerns/has_repository.rb b/app/models/concerns/has_repository.rb
index 3dea4a9f5fb..9692941d8b2 100644
--- a/app/models/concerns/has_repository.rb
+++ b/app/models/concerns/has_repository.rb
@@ -88,7 +88,7 @@ module HasRepository
group_branch_default_name = group&.default_branch_name if respond_to?(:group)
- group_branch_default_name || Gitlab::CurrentSettings.default_branch_name
+ (group_branch_default_name || Gitlab::CurrentSettings.default_branch_name).presence
end
def reload_default_branch
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
index ed22d4ba231..4f8f86965d7 100644
--- a/app/models/custom_emoji.rb
+++ b/app/models/custom_emoji.rb
@@ -17,7 +17,7 @@ class CustomEmoji < ApplicationRecord
uniqueness: { scope: [:namespace_id, :name] },
presence: true,
length: { maximum: 36 },
- format: { with: /\A([a-z0-9]+[-_]?)+[a-z0-9]+\z/ }
+ format: { with: /\A[a-z0-9][a-z0-9\-_]*[a-z0-9]\z/ }
private
diff --git a/app/models/terraform/state.rb b/app/models/terraform/state.rb
index 19700587f09..1b99f310e1a 100644
--- a/app/models/terraform/state.rb
+++ b/app/models/terraform/state.rb
@@ -3,12 +3,6 @@
module Terraform
class State < ApplicationRecord
include UsageStatistics
- include IgnorableColumns
- # These columns are being removed since geo replication falls to the versioned state
- # Tracking in https://gitlab.com/gitlab-org/gitlab/-/issues/258262
- ignore_columns %i[verification_failure verification_retry_at verified_at verification_retry_count verification_checksum],
- remove_with: '13.7',
- remove_after: '2020-12-22'
HEX_REGEXP = %r{\A\h+\z}.freeze
UUID_LENGTH = 32
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index bc8e6a6d9cc..7d5cef2015d 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -10,10 +10,12 @@
#js-new-pipeline{ data: { project_id: @project.id,
pipelines_path: project_pipelines_path(@project),
config_variables_path: config_variables_namespace_project_pipelines_path(@project.namespace, @project),
+ default_branch: @project.default_branch,
ref_param: params[:ref] || @project.default_branch,
var_param: params[:var].to_json,
file_param: params[:file_var].to_json,
- ref_names: @project.repository.ref_names.to_json.html_safe,
+ branch_refs: @project.repository.branch_names.to_json.html_safe,
+ tag_refs: @project.repository.tag_names.to_json.html_safe,
settings_link: project_settings_ci_cd_path(@project),
max_warnings: ::Gitlab::Ci::Warnings::MAX_LIMIT } }
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index f6ecb923100..0bef82ee325 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -66,11 +66,11 @@
%section.settings.no-animate#js-registry-policies{ class: ('expanded' if expanded) }
.settings-header
%h4
- = _("Cleanup policy for tags")
+ = _("Clean up image tags")
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
- = _("Save space and find tags in the Container Registry more easily. Enable the cleanup policy to remove stale tags and keep only the ones you need.")
+ = _("Save space and find images in the Container Registry. Remove unneeded tags and keep only the ones you want.")
= link_to _('More information'), help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy', target: '_blank', rel: 'noopener noreferrer')
.settings-content
= render 'projects/registry/settings/index'