summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-08 15:08:21 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-08 15:08:21 +0000
commit1bdb3fe3821fc3d222361d8b2e2ec2fea2915372 (patch)
treec2566a9cb9d3328bff5d816278112c97be4179fe
parent996d54a81d799e6a69098b1e397a4ee7ea6d200c (diff)
downloadgitlab-ce-1bdb3fe3821fc3d222361d8b2e2ec2fea2915372.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/registry/explorer/components/project_policy_alert.vue69
-rw-r--r--app/assets/javascripts/registry/explorer/constants.js47
-rw-r--r--app/assets/javascripts/registry/explorer/pages/list.vue4
-rw-r--r--app/assets/javascripts/registry/explorer/stores/mutations.js1
-rw-r--r--app/views/projects/registry/repositories/index.html.haml3
-rw-r--r--locale/gitlab.pot63
-rw-r--r--spec/frontend/registry/explorer/components/project_policy_alert_spec.js132
-rw-r--r--spec/frontend/registry/explorer/stores/mutations_spec.js7
8 files changed, 282 insertions, 44 deletions
diff --git a/app/assets/javascripts/registry/explorer/components/project_policy_alert.vue b/app/assets/javascripts/registry/explorer/components/project_policy_alert.vue
new file mode 100644
index 00000000000..6acf366e531
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/components/project_policy_alert.vue
@@ -0,0 +1,69 @@
+<script>
+import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
+import { mapState } from 'vuex';
+import { approximateDuration, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility';
+import {
+ EXPIRATION_POLICY_ALERT_TITLE,
+ EXPIRATION_POLICY_ALERT_PRIMARY_BUTTON,
+ EXPIRATION_POLICY_ALERT_FULL_MESSAGE,
+ EXPIRATION_POLICY_ALERT_SHORT_MESSAGE,
+} from '../constants';
+
+export default {
+ components: {
+ GlAlert,
+ GlSprintf,
+ GlLink,
+ },
+
+ computed: {
+ ...mapState(['config', 'images', 'isLoading']),
+ isEmpty() {
+ return !this.images || this.images.length === 0;
+ },
+ showAlert() {
+ return this.config.expirationPolicy?.enabled;
+ },
+ timeTillRun() {
+ const difference = calculateRemainingMilliseconds(this.config.expirationPolicy?.next_run_at);
+ return approximateDuration(difference / 1000);
+ },
+ alertConfiguration() {
+ if (this.isEmpty || this.isLoading) {
+ return {
+ title: null,
+ primaryButton: null,
+ message: EXPIRATION_POLICY_ALERT_SHORT_MESSAGE,
+ };
+ }
+ return {
+ title: EXPIRATION_POLICY_ALERT_TITLE,
+ primaryButton: EXPIRATION_POLICY_ALERT_PRIMARY_BUTTON,
+ message: EXPIRATION_POLICY_ALERT_FULL_MESSAGE,
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-alert
+ v-if="showAlert"
+ :dismissible="false"
+ :primary-button-text="alertConfiguration.primaryButton"
+ :primary-button-link="config.settingsPath"
+ :title="alertConfiguration.title"
+ class="my-2"
+ >
+ <gl-sprintf :message="alertConfiguration.message">
+ <template #days>
+ <strong>{{ timeTillRun }}</strong>
+ </template>
+ <template #link="{content}">
+ <gl-link :href="config.expirationPolicyHelpPagePath" target="_blank">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </gl-alert>
+</template>
diff --git a/app/assets/javascripts/registry/explorer/constants.js b/app/assets/javascripts/registry/explorer/constants.js
index bb311157627..ef72c085972 100644
--- a/app/assets/javascripts/registry/explorer/constants.js
+++ b/app/assets/javascripts/registry/explorer/constants.js
@@ -1,18 +1,24 @@
-import { __ } from '~/locale';
+import { s__ } from '~/locale';
-export const FETCH_IMAGES_LIST_ERROR_MESSAGE = __(
- 'Something went wrong while fetching the packages list.',
+export const FETCH_IMAGES_LIST_ERROR_MESSAGE = s__(
+ 'ContainerRegistry|Something went wrong while fetching the packages list.',
);
-export const FETCH_TAGS_LIST_ERROR_MESSAGE = __(
- 'Something went wrong while fetching the tags list.',
+export const FETCH_TAGS_LIST_ERROR_MESSAGE = s__(
+ 'ContainerRegistry|Something went wrong while fetching the tags list.',
);
-export const DELETE_IMAGE_ERROR_MESSAGE = __('Something went wrong while deleting the image.');
-export const DELETE_IMAGE_SUCCESS_MESSAGE = __('Image deleted successfully');
-export const DELETE_TAG_ERROR_MESSAGE = __('Something went wrong while deleting the tag.');
-export const DELETE_TAG_SUCCESS_MESSAGE = __('Tag deleted successfully');
-export const DELETE_TAGS_ERROR_MESSAGE = __('Something went wrong while deleting the tags.');
-export const DELETE_TAGS_SUCCESS_MESSAGE = __('Tags deleted successfully');
+export const DELETE_IMAGE_ERROR_MESSAGE = s__(
+ 'ContainerRegistry|Something went wrong while deleting the image.',
+);
+export const DELETE_IMAGE_SUCCESS_MESSAGE = s__('ContainerRegistry|Image deleted successfully');
+export const DELETE_TAG_ERROR_MESSAGE = s__(
+ 'ContainerRegistry|Something went wrong while deleting the tag.',
+);
+export const DELETE_TAG_SUCCESS_MESSAGE = s__('ContainerRegistry|Tag deleted successfully');
+export const DELETE_TAGS_ERROR_MESSAGE = s__(
+ 'ContainerRegistry|Something went wrong while deleting the tags.',
+);
+export const DELETE_TAGS_SUCCESS_MESSAGE = s__('ContainerRegistry|Tags deleted successfully');
export const DEFAULT_PAGE = 1;
export const DEFAULT_PAGE_SIZE = 10;
@@ -26,7 +32,18 @@ export const LIST_KEY_LAST_UPDATED = 'created_at';
export const LIST_KEY_ACTIONS = 'actions';
export const LIST_KEY_CHECKBOX = 'checkbox';
-export const LIST_LABEL_TAG = __('Tag');
-export const LIST_LABEL_IMAGE_ID = __('Image ID');
-export const LIST_LABEL_SIZE = __('Size');
-export const LIST_LABEL_LAST_UPDATED = __('Last Updated');
+export const LIST_LABEL_TAG = s__('ContainerRegistry|Tag');
+export const LIST_LABEL_IMAGE_ID = s__('ContainerRegistry|Image ID');
+export const LIST_LABEL_SIZE = s__('ContainerRegistry|Size');
+export const LIST_LABEL_LAST_UPDATED = s__('ContainerRegistry|Last Updated');
+
+export const EXPIRATION_POLICY_ALERT_TITLE = s__(
+ 'ContainerRegistry|Retention policy has been Enabled',
+);
+export const EXPIRATION_POLICY_ALERT_PRIMARY_BUTTON = s__('ContainerRegistry|Edit Settings');
+export const EXPIRATION_POLICY_ALERT_FULL_MESSAGE = s__(
+ 'ContainerRegistry|The retention and expiration policy for this Container Registry has been enabled and will run in %{days}. For more information visit the %{linkStart}documentation%{linkEnd}',
+);
+export const EXPIRATION_POLICY_ALERT_SHORT_MESSAGE = s__(
+ 'ContainerRegistry|The retention and expiration policy for this Container Registry has been enabled. For more information visit the %{linkStart}documentation%{linkEnd}',
+);
diff --git a/app/assets/javascripts/registry/explorer/pages/list.vue b/app/assets/javascripts/registry/explorer/pages/list.vue
index 4e9f0a83501..c6ba06cd68c 100644
--- a/app/assets/javascripts/registry/explorer/pages/list.vue
+++ b/app/assets/javascripts/registry/explorer/pages/list.vue
@@ -15,6 +15,7 @@ import Tracking from '~/tracking';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ProjectEmptyState from '../components/project_empty_state.vue';
import GroupEmptyState from '../components/group_empty_state.vue';
+import ProjectPolicyAlert from '../components/project_policy_alert.vue';
export default {
name: 'RegistryListApp',
@@ -23,6 +24,7 @@ export default {
GlPagination,
ProjectEmptyState,
GroupEmptyState,
+ ProjectPolicyAlert,
ClipboardButton,
GlButton,
GlIcon,
@@ -84,6 +86,8 @@ export default {
<template>
<div class="w-100 slide-enter-from-element">
+ <project-policy-alert v-if="!config.isGroupPage" />
+
<gl-empty-state
v-if="config.characterError"
:title="s__('ContainerRegistry|Docker connection error')"
diff --git a/app/assets/javascripts/registry/explorer/stores/mutations.js b/app/assets/javascripts/registry/explorer/stores/mutations.js
index a2c6a11de20..6055efcbd46 100644
--- a/app/assets/javascripts/registry/explorer/stores/mutations.js
+++ b/app/assets/javascripts/registry/explorer/stores/mutations.js
@@ -5,6 +5,7 @@ export default {
[types.SET_INITIAL_STATE](state, config) {
state.config = {
...config,
+ expirationPolicy: config.expirationPolicy ? JSON.parse(config.expirationPolicy) : undefined,
isGroupPage: config.isGroupPage !== undefined,
};
},
diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml
index 62b744b5095..810830fd0c4 100644
--- a/app/views/projects/registry/repositories/index.html.haml
+++ b/app/views/projects/registry/repositories/index.html.haml
@@ -6,6 +6,8 @@
.col-12
- if Feature.enabled?(:vue_container_registry_explorer, @project.group)
#js-container-registry{ data: { endpoint: project_container_registry_index_path(@project),
+ settings_path: project_settings_ci_cd_path(@project),
+ expiration_policy: @project.container_expiration_policy.to_json,
"help_page_path" => help_page_path('user/packages/container_registry/index'),
"two_factor_auth_help_link" => help_page_path('user/profile/account/two_factor_authentication'),
"personal_access_tokens_help_link" => help_page_path('user/profile/personal_access_tokens'),
@@ -13,6 +15,7 @@
"containers_error_image" => image_path('illustrations/docker-error-state.svg'),
"repository_url" => escape_once(@project.container_registry_url),
"registry_host_url_with_port" => escape_once(registry_config.host_port),
+ "expiration_policy_help_page_path" => help_page_path('user/packages/container_registry/index', anchor: 'expiration-policy'),
character_error: @character_error.to_s } }
- else
#js-vue-registry-images{ data: { endpoint: project_container_registry_index_path(@project, format: :json),
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 18cfcea9335..4d917cbe943 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5250,6 +5250,9 @@ msgstr ""
msgid "ContainerRegistry|Docker tags with names matching this regex pattern will expire:"
msgstr ""
+msgid "ContainerRegistry|Edit Settings"
+msgstr ""
+
msgid "ContainerRegistry|Expiration interval:"
msgstr ""
@@ -5268,6 +5271,9 @@ msgstr ""
msgid "ContainerRegistry|Image ID"
msgstr ""
+msgid "ContainerRegistry|Image deleted successfully"
+msgstr ""
+
msgid "ContainerRegistry|Keep and protect the images that matter most."
msgstr ""
@@ -5294,27 +5300,57 @@ msgid_plural "ContainerRegistry|Remove tags"
msgstr[0] ""
msgstr[1] ""
+msgid "ContainerRegistry|Retention policy has been Enabled"
+msgstr ""
+
msgid "ContainerRegistry|Size"
msgstr ""
+msgid "ContainerRegistry|Something went wrong while deleting the image."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while deleting the tag."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while deleting the tags."
+msgstr ""
+
msgid "ContainerRegistry|Something went wrong while fetching the expiration policy."
msgstr ""
+msgid "ContainerRegistry|Something went wrong while fetching the packages list."
+msgstr ""
+
+msgid "ContainerRegistry|Something went wrong while fetching the tags list."
+msgstr ""
+
msgid "ContainerRegistry|Something went wrong while updating the expiration policy."
msgstr ""
msgid "ContainerRegistry|Tag"
msgstr ""
+msgid "ContainerRegistry|Tag deleted successfully"
+msgstr ""
+
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
msgid "ContainerRegistry|Tag expiration policy is designed to:"
msgstr ""
+msgid "ContainerRegistry|Tags deleted successfully"
+msgstr ""
+
msgid "ContainerRegistry|The last tag related to this image was recently removed. This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process. If you have any questions, contact your administrator."
msgstr ""
+msgid "ContainerRegistry|The retention and expiration policy for this Container Registry has been enabled and will run in %{days}. For more information visit the %{linkStart}documentation%{linkEnd}"
+msgstr ""
+
+msgid "ContainerRegistry|The retention and expiration policy for this Container Registry has been enabled. For more information visit the %{linkStart}documentation%{linkEnd}"
+msgstr ""
+
msgid "ContainerRegistry|The value of this input should be less than 255 characters"
msgstr ""
@@ -10500,12 +10536,6 @@ msgstr ""
msgid "Image %{imageName} was scheduled for deletion from the registry."
msgstr ""
-msgid "Image ID"
-msgstr ""
-
-msgid "Image deleted successfully"
-msgstr ""
-
msgid "Image: %{image}"
msgstr ""
@@ -11374,9 +11404,6 @@ msgstr ""
msgid "Last Seen"
msgstr ""
-msgid "Last Updated"
-msgstr ""
-
msgid "Last accessed on"
msgstr ""
@@ -18192,21 +18219,12 @@ msgstr ""
msgid "Something went wrong while deleting description changes. Please try again."
msgstr ""
-msgid "Something went wrong while deleting the image."
-msgstr ""
-
msgid "Something went wrong while deleting the package."
msgstr ""
msgid "Something went wrong while deleting the source branch. Please try again."
msgstr ""
-msgid "Something went wrong while deleting the tag."
-msgstr ""
-
-msgid "Something went wrong while deleting the tags."
-msgstr ""
-
msgid "Something went wrong while deleting your note. Please try again."
msgstr ""
@@ -18249,9 +18267,6 @@ msgstr ""
msgid "Something went wrong while fetching the registry list."
msgstr ""
-msgid "Something went wrong while fetching the tags list."
-msgstr ""
-
msgid "Something went wrong while initializing the OpenAPI viewer"
msgstr ""
@@ -19074,9 +19089,6 @@ msgstr ""
msgid "Tag"
msgstr ""
-msgid "Tag deleted successfully"
-msgstr ""
-
msgid "Tag list:"
msgstr ""
@@ -19095,9 +19107,6 @@ msgstr ""
msgid "Tags"
msgstr ""
-msgid "Tags deleted successfully"
-msgstr ""
-
msgid "Tags feed"
msgstr ""
diff --git a/spec/frontend/registry/explorer/components/project_policy_alert_spec.js b/spec/frontend/registry/explorer/components/project_policy_alert_spec.js
new file mode 100644
index 00000000000..89c37e55398
--- /dev/null
+++ b/spec/frontend/registry/explorer/components/project_policy_alert_spec.js
@@ -0,0 +1,132 @@
+import Vuex from 'vuex';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlSprintf, GlAlert, GlLink } from '@gitlab/ui';
+import * as dateTimeUtils from '~/lib/utils/datetime_utility';
+import component from '~/registry/explorer/components/project_policy_alert.vue';
+import {
+ EXPIRATION_POLICY_ALERT_TITLE,
+ EXPIRATION_POLICY_ALERT_PRIMARY_BUTTON,
+} from '~/registry/explorer/constants';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('Project Policy Alert', () => {
+ let wrapper;
+ let store;
+
+ const defaultState = {
+ config: {
+ expirationPolicy: {
+ enabled: true,
+ },
+ settingsPath: 'foo',
+ expirationPolicyHelpPagePath: 'bar',
+ },
+ images: [],
+ isLoading: false,
+ };
+
+ const findAlert = () => wrapper.find(GlAlert);
+ const findLink = () => wrapper.find(GlLink);
+
+ const createComponent = (state = defaultState) => {
+ store = new Vuex.Store({
+ state,
+ });
+ wrapper = shallowMount(component, {
+ localVue,
+ store,
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ const documentationExpectation = () => {
+ it('contain a documentation link', () => {
+ createComponent();
+ expect(findLink().attributes('href')).toBe(defaultState.config.expirationPolicyHelpPagePath);
+ expect(findLink().text()).toBe('documentation');
+ });
+ };
+
+ beforeEach(() => {
+ jest.spyOn(dateTimeUtils, 'approximateDuration').mockReturnValue('1 day');
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('is hidden', () => {
+ it('when expiration policy does not exist', () => {
+ createComponent({ config: {} });
+ expect(findAlert().exists()).toBe(false);
+ });
+
+ it('when expiration policy exist but is disabled', () => {
+ createComponent({
+ ...defaultState,
+ config: {
+ expirationPolicy: {
+ enabled: false,
+ },
+ },
+ });
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+
+ describe('is visible', () => {
+ it('when expiration policy exists and is enabled', () => {
+ createComponent();
+ expect(findAlert().exists()).toBe(true);
+ });
+ });
+
+ describe('full info alert', () => {
+ beforeEach(() => {
+ createComponent({ ...defaultState, images: [1] });
+ });
+
+ it('has a primary button', () => {
+ const alert = findAlert();
+ expect(alert.props('primaryButtonText')).toBe(EXPIRATION_POLICY_ALERT_PRIMARY_BUTTON);
+ expect(alert.props('primaryButtonLink')).toBe(defaultState.config.settingsPath);
+ });
+
+ it('has a title', () => {
+ const alert = findAlert();
+ expect(alert.props('title')).toBe(EXPIRATION_POLICY_ALERT_TITLE);
+ });
+
+ it('has the full message', () => {
+ expect(findAlert().html()).toContain('<strong>1 day</strong>');
+ });
+
+ documentationExpectation();
+ });
+
+ describe('compact info alert', () => {
+ beforeEach(() => {
+ createComponent({ ...defaultState, images: [] });
+ });
+
+ it('does not have a button', () => {
+ const alert = findAlert();
+ expect(alert.props('primaryButtonText')).toBe(null);
+ });
+
+ it('does not have a title', () => {
+ const alert = findAlert();
+ expect(alert.props('title')).toBe(null);
+ });
+
+ it('has the short message', () => {
+ expect(findAlert().html()).not.toContain('<strong>1 day</strong>');
+ });
+
+ documentationExpectation();
+ });
+});
diff --git a/spec/frontend/registry/explorer/stores/mutations_spec.js b/spec/frontend/registry/explorer/stores/mutations_spec.js
index 43f6a95db10..1d5055c02d2 100644
--- a/spec/frontend/registry/explorer/stores/mutations_spec.js
+++ b/spec/frontend/registry/explorer/stores/mutations_spec.js
@@ -10,9 +10,12 @@ describe('Mutations Registry Explorer Store', () => {
describe('SET_INITIAL_STATE', () => {
it('should set the initial state', () => {
- const payload = { endpoint: 'foo', isGroupPage: true };
+ const payload = { endpoint: 'foo', isGroupPage: true, expirationPolicy: { foo: 'bar' } };
const expectedState = { ...mockState, config: payload };
- mutations[types.SET_INITIAL_STATE](mockState, payload);
+ mutations[types.SET_INITIAL_STATE](mockState, {
+ ...payload,
+ expirationPolicy: JSON.stringify(payload.expirationPolicy),
+ });
expect(mockState).toEqual(expectedState);
});