Commit 238160c2 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch...

Merge branch '218545-refactor-container-registry-frontend-code-to-ease-community-contribution-2' into 'master'

Split container registry details page

See merge request gitlab-org/gitlab!33829
parents bcd225d7 9645e8e0
<script>
import { GlSprintf, GlAlert, GlLink } from '@gitlab/ui';
import { ALERT_MESSAGES, ADMIN_GARBAGE_COLLECTION_TIP } from '../../constants/index';
export default {
components: {
GlSprintf,
GlAlert,
GlLink,
},
model: {
prop: 'deleteAlertType',
event: 'change',
},
props: {
deleteAlertType: {
type: String,
default: null,
required: false,
validator(value) {
return !value || ALERT_MESSAGES[value] !== undefined;
},
},
garbageCollectionHelpPagePath: { type: String, required: false, default: '' },
isAdmin: {
type: Boolean,
default: false,
required: false,
},
},
computed: {
deleteAlertConfig() {
const config = {
title: '',
message: '',
type: 'success',
};
if (this.deleteAlertType) {
[config.type] = this.deleteAlertType.split('_');
config.message = ALERT_MESSAGES[this.deleteAlertType];
if (this.isAdmin && config.type === 'success') {
config.title = config.message;
config.message = ADMIN_GARBAGE_COLLECTION_TIP;
}
}
return config;
},
},
};
</script>
<template>
<gl-alert
v-if="deleteAlertType"
:variant="deleteAlertConfig.type"
:title="deleteAlertConfig.title"
@dismiss="$emit('change', null)"
>
<gl-sprintf :message="deleteAlertConfig.message">
<template #docLink="{content}">
<gl-link :href="garbageCollectionHelpPagePath" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</gl-alert>
</template>
<script>
import { GlModal, GlSprintf } from '@gitlab/ui';
import { n__ } from '~/locale';
import { REMOVE_TAG_CONFIRMATION_TEXT, REMOVE_TAGS_CONFIRMATION_TEXT } from '../../constants/index';
export default {
components: {
GlModal,
GlSprintf,
},
props: {
itemsToBeDeleted: {
type: Array,
required: false,
default: () => [],
},
},
computed: {
modalAction() {
return n__(
'ContainerRegistry|Remove tag',
'ContainerRegistry|Remove tags',
this.itemsToBeDeleted.length,
);
},
modalDescription() {
if (this.itemsToBeDeleted.length > 1) {
return {
message: REMOVE_TAGS_CONFIRMATION_TEXT,
item: this.itemsToBeDeleted.length,
};
}
const [first] = this.itemsToBeDeleted;
return {
message: REMOVE_TAG_CONFIRMATION_TEXT,
item: first?.path,
};
},
},
methods: {
show() {
this.$refs.deleteModal.show();
},
},
};
</script>
<template>
<gl-modal
ref="deleteModal"
modal-id="delete-tag-modal"
ok-variant="danger"
@ok="$emit('confirmDelete')"
@cancel="$emit('cancelDelete')"
>
<template #modal-title>{{ modalAction }}</template>
<template #modal-ok>{{ modalAction }}</template>
<p v-if="modalDescription" data-testid="description">
<gl-sprintf :message="modalDescription.message">
<template #item
><b>{{ modalDescription.item }}</b></template
>
</gl-sprintf>
</p>
</gl-modal>
</template>
<script>
import { GlSprintf } from '@gitlab/ui';
import { DETAILS_PAGE_TITLE } from '../../constants/index';
export default {
components: { GlSprintf },
props: {
imageName: {
type: String,
required: false,
default: '',
},
},
i18n: {
DETAILS_PAGE_TITLE,
},
};
</script>
<template>
<div class="gl-display-flex gl-my-2 gl-align-items-center">
<h4>
<gl-sprintf :message="$options.i18n.DETAILS_PAGE_TITLE">
<template #imageName>
{{ imageName }}
</template>
</gl-sprintf>
</h4>
</div>
</template>
......@@ -47,3 +47,14 @@ export const LIST_KEY_SIZE = 'total_size';
export const LIST_KEY_LAST_UPDATED = 'created_at';
export const LIST_KEY_ACTIONS = 'actions';
export const LIST_KEY_CHECKBOX = 'checkbox';
export const ALERT_SUCCESS_TAG = 'success_tag';
export const ALERT_DANGER_TAG = 'danger_tag';
export const ALERT_SUCCESS_TAGS = 'success_tags';
export const ALERT_DANGER_TAGS = 'danger_tags';
export const ALERT_MESSAGES = {
[ALERT_SUCCESS_TAG]: DELETE_TAG_SUCCESS_MESSAGE,
[ALERT_DANGER_TAG]: DELETE_TAG_ERROR_MESSAGE,
[ALERT_SUCCESS_TAGS]: DELETE_TAGS_SUCCESS_MESSAGE,
[ALERT_DANGER_TAGS]: DELETE_TAGS_ERROR_MESSAGE,
};
import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import component from '~/registry/explorer/components/details_page/delete_alert.vue';
import {
DELETE_TAG_SUCCESS_MESSAGE,
DELETE_TAG_ERROR_MESSAGE,
DELETE_TAGS_SUCCESS_MESSAGE,
DELETE_TAGS_ERROR_MESSAGE,
ADMIN_GARBAGE_COLLECTION_TIP,
} from '~/registry/explorer/constants';
describe('Delete alert', () => {
let wrapper;
const findAlert = () => wrapper.find(GlAlert);
const findLink = () => wrapper.find(GlLink);
const mountComponent = propsData => {
wrapper = shallowMount(component, { stubs: { GlSprintf }, propsData });
};
describe('when deleteAlertType is null', () => {
it('does not show the alert', () => {
mountComponent();
expect(findAlert().exists()).toBe(false);
});
});
describe('when deleteAlertType is not null', () => {
describe('success states', () => {
describe.each`
deleteAlertType | message
${'success_tag'} | ${DELETE_TAG_SUCCESS_MESSAGE}
${'success_tags'} | ${DELETE_TAGS_SUCCESS_MESSAGE}
`('when deleteAlertType is $deleteAlertType', ({ deleteAlertType, message }) => {
it('alert exists', () => {
mountComponent({ deleteAlertType });
expect(findAlert().exists()).toBe(true);
});
describe('when the user is an admin', () => {
beforeEach(() => {
mountComponent({
deleteAlertType,
isAdmin: true,
garbageCollectionHelpPagePath: 'foo',
});
});
it(`alert title is ${message}`, () => {
expect(findAlert().attributes('title')).toBe(message);
});
it('alert body contains admin tip', () => {
expect(findAlert().text()).toMatchInterpolatedText(ADMIN_GARBAGE_COLLECTION_TIP);
});
it('alert body contains link', () => {
const alertLink = findLink();
expect(alertLink.exists()).toBe(true);
expect(alertLink.attributes('href')).toBe('foo');
});
});
describe('when the user is not an admin', () => {
it('alert exist and text is appropriate', () => {
mountComponent({ deleteAlertType });
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toBe(message);
});
});
});
});
describe('error states', () => {
describe.each`
deleteAlertType | message
${'danger_tag'} | ${DELETE_TAG_ERROR_MESSAGE}
${'danger_tags'} | ${DELETE_TAGS_ERROR_MESSAGE}
`('when deleteAlertType is $deleteAlertType', ({ deleteAlertType, message }) => {
it('alert exists', () => {
mountComponent({ deleteAlertType });
expect(findAlert().exists()).toBe(true);
});
describe('when the user is an admin', () => {
it('alert exist and text is appropriate', () => {
mountComponent({ deleteAlertType });
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toBe(message);
});
});
describe('when the user is not an admin', () => {
it('alert exist and text is appropriate', () => {
mountComponent({ deleteAlertType });
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toBe(message);
});
});
});
});
describe('dismissing alert', () => {
it('GlAlert dismiss event triggers a change event', () => {
mountComponent({ deleteAlertType: 'success_tags' });
findAlert().vm.$emit('dismiss');
expect(wrapper.emitted('change')).toEqual([[null]]);
});
});
});
});
import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
import component from '~/registry/explorer/components/details_page/delete_modal.vue';
import {
REMOVE_TAG_CONFIRMATION_TEXT,
REMOVE_TAGS_CONFIRMATION_TEXT,
} from '~/registry/explorer/constants';
import { GlModal } from '../../stubs';
describe('Delete Modal', () => {
let wrapper;
const findModal = () => wrapper.find(GlModal);
const findDescription = () => wrapper.find('[data-testid="description"]');
const mountComponent = propsData => {
wrapper = shallowMount(component, {
propsData,
stubs: {
GlSprintf,
GlModal,
},
});
};
it('contains a GlModal', () => {
mountComponent();
expect(findModal().exists()).toBe(true);
});
describe('events', () => {
it.each`
glEvent | localEvent
${'ok'} | ${'confirmDelete'}
${'cancel'} | ${'cancelDelete'}
`('GlModal $glEvent emits $localEvent', ({ glEvent, localEvent }) => {
mountComponent();
findModal().vm.$emit(glEvent);
expect(wrapper.emitted(localEvent)).toBeTruthy();
});
});
describe('methods', () => {
it('show calls gl-modal show', () => {
mountComponent();
wrapper.vm.show();
expect(GlModal.methods.show).toHaveBeenCalled();
});
});
describe('itemsToBeDeleted contains one element', () => {
beforeEach(() => {
mountComponent({ itemsToBeDeleted: [{ path: 'foo' }] });
});
it(`has the correct description`, () => {
expect(findDescription().text()).toBe(REMOVE_TAG_CONFIRMATION_TEXT.replace('%{item}', 'foo'));
});
it('has the correct action', () => {
expect(wrapper.text()).toContain('Remove tag');
});
});
describe('itemsToBeDeleted contains more than element', () => {
beforeEach(() => {
mountComponent({ itemsToBeDeleted: [{ path: 'foo' }, { path: 'bar' }] });
});
it(`has the correct description`, () => {
expect(findDescription().text()).toBe(REMOVE_TAGS_CONFIRMATION_TEXT.replace('%{item}', '2'));
});
it('has the correct action', () => {
expect(wrapper.text()).toContain('Remove tags');
});
});
});
import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
import component from '~/registry/explorer/components/details_page/details_header.vue';
import { DETAILS_PAGE_TITLE } from '~/registry/explorer/constants';
describe('Details Header', () => {
let wrapper;
const mountComponent = propsData => {
wrapper = shallowMount(component, {
propsData,
stubs: {
GlSprintf,
},
});
};
it('has the correct title ', () => {
mountComponent();
expect(wrapper.text()).toMatchInterpolatedText(DETAILS_PAGE_TITLE);
});
it('shows imageName in the title', () => {
mountComponent({ imageName: 'foo' });
expect(wrapper.text()).toContain('foo');
});
});
......@@ -64,7 +64,7 @@ export const imagesListResponse = {
export const tagsListResponse = {
data: [
{
tag: 'centos6',
name: 'centos6',
revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
short_revision: 'b118ab5b0',
size: 19,
......@@ -75,7 +75,7 @@ export const tagsListResponse = {
destroy_path: 'path',
},
{
tag: 'test-image',
name: 'test-tag',
revision: 'b969de599faea2b3d9b6605a8b0897261c571acaa36db1bdc7349b5775b4e0b4',
short_revision: 'b969de599',
size: 19,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment