Commit 9253812f authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '331992-cleanup-timeout-warning-icon-display-bug' into 'master'

Add cleanup status by row component

See merge request gitlab-org/gitlab!65320
parents 8ea9b0e2 94709de7
<script>
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
import {
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
CLEANUP_STATUS_SCHEDULED,
CLEANUP_STATUS_ONGOING,
CLEANUP_STATUS_UNFINISHED,
UNFINISHED_STATUS,
UNSCHEDULED_STATUS,
SCHEDULED_STATUS,
ONGOING_STATUS,
} from '../../constants/index';
export default {
name: 'CleanupStatus',
components: {
GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
status: {
type: String,
required: true,
validator(value) {
return [UNFINISHED_STATUS, UNSCHEDULED_STATUS, SCHEDULED_STATUS, ONGOING_STATUS].includes(
value,
);
},
},
},
i18n: {
CLEANUP_STATUS_SCHEDULED,
CLEANUP_STATUS_ONGOING,
CLEANUP_STATUS_UNFINISHED,
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
},
computed: {
showStatus() {
return this.status !== UNSCHEDULED_STATUS;
},
failedDelete() {
return this.status === UNFINISHED_STATUS;
},
statusText() {
return this.$options.i18n[`CLEANUP_STATUS_${this.status}`];
},
expireIconClass() {
return this.failedDelete ? 'gl-text-orange-500' : '';
},
},
};
</script>
<template>
<div v-if="showStatus" class="gl-display-inline-flex gl-align-items-center">
<gl-icon name="expire" data-testid="main-icon" :class="expireIconClass" />
<span class="gl-mx-2">
{{ statusText }}
</span>
<gl-icon
v-if="failedDelete"
v-gl-tooltip="{ title: $options.i18n.ASYNC_DELETE_IMAGE_ERROR_MESSAGE }"
:size="14"
class="gl-text-black-normal"
data-testid="extra-info"
name="information"
/>
</div>
</template>
......@@ -16,6 +16,7 @@ import {
ROOT_IMAGE_TEXT,
} from '../../constants/index';
import DeleteButton from '../delete_button.vue';
import CleanupStatus from './cleanup_status.vue';
export default {
name: 'ImageListRow',
......@@ -26,6 +27,7 @@ export default {
GlIcon,
ListItem,
GlSkeletonLoader,
CleanupStatus,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -112,20 +114,10 @@ export default {
:title="item.location"
category="tertiary"
/>
<gl-icon
v-if="warningIconText"
v-gl-tooltip="{ title: warningIconText }"
data-testid="warning-icon"
name="warning"
class="gl-text-orange-500"
/>
</template>
<template #left-secondary>
<span
v-if="!metadataLoading"
class="gl-display-flex gl-align-items-center"
data-testid="tags-count"
>
<template v-if="!metadataLoading">
<span class="gl-display-flex gl-align-items-center" data-testid="tags-count">
<gl-icon name="tag" class="gl-mr-2" />
<gl-sprintf :message="tagsCountText">
<template #count>
......@@ -134,6 +126,13 @@ export default {
</gl-sprintf>
</span>
<cleanup-status
v-if="item.expirationPolicyCleanupStatus"
class="ml-2"
:status="item.expirationPolicyCleanupStatus"
/>
</template>
<div v-else class="gl-w-full">
<gl-skeleton-loader :width="900" :height="16" preserve-aspect-ratio="xMinYMax meet">
<circle cx="6" cy="8" r="6" />
......
......@@ -89,6 +89,10 @@ export const CLEANUP_DISABLED_TOOLTIP = s__(
'ContainerRegistry|Cleanup is disabled for this project',
);
export const CLEANUP_STATUS_SCHEDULED = s__('ContainerRegistry|Cleanup will run soon');
export const CLEANUP_STATUS_ONGOING = s__('ContainerRegistry|Cleanup is ongoing');
export const CLEANUP_STATUS_UNFINISHED = s__('ContainerRegistry|Cleanup timed out');
export const DETAILS_DELETE_IMAGE_ERROR_MESSAGE = s__(
'ContainerRegistry|Something went wrong while scheduling the image for deletion.',
);
......
......@@ -29,6 +29,7 @@ query getProjectContainerRepositories(
canDelete
createdAt
expirationPolicyStartedAt
expirationPolicyCleanupStatus
__typename
}
pageInfo {
......@@ -61,6 +62,7 @@ query getProjectContainerRepositories(
canDelete
createdAt
expirationPolicyStartedAt
expirationPolicyCleanupStatus
__typename
}
pageInfo {
......
......@@ -8490,6 +8490,9 @@ msgstr ""
msgid "ContainerRegistry|Cleanup is disabled for this project"
msgstr ""
msgid "ContainerRegistry|Cleanup is ongoing"
msgstr ""
msgid "ContainerRegistry|Cleanup pending"
msgstr ""
......@@ -8505,6 +8508,9 @@ msgstr ""
msgid "ContainerRegistry|Cleanup ran but some tags were not removed"
msgstr ""
msgid "ContainerRegistry|Cleanup timed out"
msgstr ""
msgid "ContainerRegistry|Cleanup timed out before it could delete all tags"
msgstr ""
......
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import CleanupStatus from '~/registry/explorer/components/list_page/cleanup_status.vue';
import {
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
CLEANUP_STATUS_SCHEDULED,
CLEANUP_STATUS_ONGOING,
CLEANUP_STATUS_UNFINISHED,
UNFINISHED_STATUS,
UNSCHEDULED_STATUS,
SCHEDULED_STATUS,
ONGOING_STATUS,
} from '~/registry/explorer/constants';
describe('cleanup_status', () => {
let wrapper;
const findMainIcon = () => wrapper.findByTestId('main-icon');
const findExtraInfoIcon = () => wrapper.findByTestId('extra-info');
const mountComponent = (propsData = { status: SCHEDULED_STATUS }) => {
wrapper = shallowMountExtended(CleanupStatus, {
propsData,
directives: {
GlTooltip: createMockDirective(),
},
});
};
afterEach(() => {
wrapper.destroy();
});
it.each`
status | visible | text
${UNFINISHED_STATUS} | ${true} | ${CLEANUP_STATUS_UNFINISHED}
${SCHEDULED_STATUS} | ${true} | ${CLEANUP_STATUS_SCHEDULED}
${ONGOING_STATUS} | ${true} | ${CLEANUP_STATUS_ONGOING}
${UNSCHEDULED_STATUS} | ${false} | ${''}
`(
'when the status is $status is $visible that the component is mounted and has the correct text',
({ status, visible, text }) => {
mountComponent({ status });
expect(findMainIcon().exists()).toBe(visible);
expect(wrapper.text()).toBe(text);
},
);
describe('main icon', () => {
it('exists', () => {
mountComponent();
expect(findMainIcon().exists()).toBe(true);
});
it(`has the orange class when the status is ${UNFINISHED_STATUS}`, () => {
mountComponent({ status: UNFINISHED_STATUS });
expect(findMainIcon().classes('gl-text-orange-500')).toBe(true);
});
});
describe('extra info icon', () => {
it.each`
status | visible
${UNFINISHED_STATUS} | ${true}
${SCHEDULED_STATUS} | ${false}
${ONGOING_STATUS} | ${false}
`(
'when the status is $status is $visible that the extra icon is visible',
({ status, visible }) => {
mountComponent({ status });
expect(findExtraInfoIcon().exists()).toBe(visible);
},
);
it(`has a tooltip`, () => {
mountComponent({ status: UNFINISHED_STATUS });
const tooltip = getBinding(findExtraInfoIcon().element, 'gl-tooltip');
expect(tooltip.value.title).toBe(ASYNC_DELETE_IMAGE_ERROR_MESSAGE);
});
});
});
......@@ -3,15 +3,14 @@ import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import DeleteButton from '~/registry/explorer/components/delete_button.vue';
import CleanupStatus from '~/registry/explorer/components/list_page/cleanup_status.vue';
import Component from '~/registry/explorer/components/list_page/image_list_row.vue';
import {
ROW_SCHEDULED_FOR_DELETION,
LIST_DELETE_BUTTON_DISABLED,
REMOVE_REPOSITORY_LABEL,
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
IMAGE_DELETE_SCHEDULED_STATUS,
IMAGE_FAILED_DELETED_STATUS,
SCHEDULED_STATUS,
ROOT_IMAGE_TEXT,
} from '~/registry/explorer/constants';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
......@@ -27,7 +26,7 @@ describe('Image List Row', () => {
const findTagsCount = () => wrapper.find('[data-testid="tags-count"]');
const findDeleteBtn = () => wrapper.findComponent(DeleteButton);
const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
const findWarningIcon = () => wrapper.find('[data-testid="warning-icon"]');
const findCleanupStatus = () => wrapper.findComponent(CleanupStatus);
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findListItemComponent = () => wrapper.findComponent(ListItem);
......@@ -106,23 +105,22 @@ describe('Image List Row', () => {
expect(button.props('title')).toBe(item.location);
});
describe('warning icon', () => {
describe('cleanup status component', () => {
it.each`
status | expirationPolicyStartedAt | shown | title
${IMAGE_FAILED_DELETED_STATUS} | ${true} | ${true} | ${ASYNC_DELETE_IMAGE_ERROR_MESSAGE}
${''} | ${true} | ${true} | ${CLEANUP_TIMED_OUT_ERROR_MESSAGE}
${''} | ${false} | ${false} | ${''}
expirationPolicyCleanupStatus | shown
${null} | ${false}
${SCHEDULED_STATUS} | ${true}
`(
'when status is $status and expirationPolicyStartedAt is $expirationPolicyStartedAt',
({ expirationPolicyStartedAt, status, shown, title }) => {
mountComponent({ item: { ...item, status, expirationPolicyStartedAt } });
'when expirationPolicyCleanupStatus is $expirationPolicyCleanupStatus it is $shown that the component exists',
({ expirationPolicyCleanupStatus, shown }) => {
mountComponent({ item: { ...item, expirationPolicyCleanupStatus } });
const icon = findWarningIcon();
expect(icon.exists()).toBe(shown);
expect(findCleanupStatus().exists()).toBe(shown);
if (shown) {
const tooltip = getBinding(icon.element, 'gl-tooltip');
expect(tooltip.value.title).toBe(title);
expect(findCleanupStatus().props()).toMatchObject({
status: expirationPolicyCleanupStatus,
});
}
},
);
......
......@@ -9,6 +9,7 @@ export const imagesListResponse = [
canDelete: true,
createdAt: '2020-11-03T13:29:21Z',
expirationPolicyStartedAt: null,
expirationPolicyCleanupStatus: 'UNSCHEDULED',
},
{
__typename: 'ContainerRepository',
......@@ -20,6 +21,7 @@ export const imagesListResponse = [
canDelete: true,
createdAt: '2020-09-21T06:57:43Z',
expirationPolicyStartedAt: null,
expirationPolicyCleanupStatus: 'UNSCHEDULED',
},
];
......
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