Commit a9f5971a authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch 'jivanvl-add-clear-cache-button-dependency-proxy' into 'master'

Add clear cache button to dependency proxy

See merge request gitlab-org/gitlab!83786
parents da506038 b5753a52
......@@ -93,6 +93,7 @@ const Api = {
notificationSettingsPath: '/api/:version/notification_settings',
deployKeysPath: '/api/:version/deploy_keys',
secureFilesPath: '/api/:version/projects/:project_id/secure_files',
dependencyProxyPath: '/api/:version/groups/:id/dependency_proxy/cache',
group(groupId, callback = () => {}) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
......@@ -999,6 +1000,12 @@ const Api = {
return result;
},
deleteDependencyProxyCacheList(groupId, options = {}) {
const url = Api.buildUrl(this.dependencyProxyPath).replace(':id', groupId);
return axios.delete(url, { params: { ...options } });
},
};
export default Api;
<script>
import {
GlAlert,
GlDropdown,
GlDropdownItem,
GlEmptyState,
GlFormGroup,
GlFormInputGroup,
GlModal,
GlModalDirective,
GlSkeletonLoader,
GlSprintf,
} from '@gitlab/ui';
import { s__ } from '~/locale';
import { __, s__, n__, sprintf } from '~/locale';
import Api from '~/api';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import ManifestsList from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue';
......@@ -22,16 +27,22 @@ import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency
export default {
components: {
GlAlert,
GlDropdown,
GlDropdownItem,
GlEmptyState,
GlFormGroup,
GlFormInputGroup,
GlModal,
GlSkeletonLoader,
GlSprintf,
ClipboardButton,
TitleArea,
ManifestsList,
},
inject: ['groupPath', 'dependencyProxyAvailable', 'noManifestsIllustration'],
directives: {
GlModalDirective,
},
inject: ['groupPath', 'groupId', 'dependencyProxyAvailable', 'noManifestsIllustration'],
i18n: {
proxyNotAvailableText: s__(
'DependencyProxy|Dependency Proxy feature is limited to public groups for now.',
......@@ -41,6 +52,19 @@ export default {
blobCountAndSize: s__('DependencyProxy|Contains %{count} blobs of images (%{size})'),
pageTitle: s__('DependencyProxy|Dependency Proxy'),
noManifestTitle: s__('DependencyProxy|There are no images in the cache'),
deleteCacheAlertMessageSuccess: s__(
'DependencyProxy|All items in the cache are scheduled for removal.',
),
},
confirmClearCacheModal: 'confirm-clear-cache-modal',
modalButtons: {
primary: {
text: s__('DependencyProxy|Clear cache'),
attributes: [{ variant: 'danger' }],
},
secondary: {
text: __('Cancel'),
},
},
links: {
DEPENDENCY_PROXY_DOCS_PATH,
......@@ -48,6 +72,8 @@ export default {
data() {
return {
group: {},
showDeleteCacheAlert: false,
deleteCacheAlertMessage: '',
};
},
apollo: {
......@@ -80,6 +106,33 @@ export default {
manifests() {
return this.group.dependencyProxyManifests.nodes;
},
modalTitleWithCount() {
return sprintf(
n__(
'Clear %{count} image from cache?',
'Clear %{count} images from cache?',
this.group.dependencyProxyBlobCount,
),
{
count: this.group.dependencyProxyBlobCount,
},
);
},
modalConfirmationMessageWithCount() {
return sprintf(
n__(
'You are about to clear %{count} image from the cache. Once you confirm, the next time a pipeline runs it must pull an image or tag from Docker Hub. Are you sure?',
'You are about to clear %{count} images from the cache. Once you confirm, the next time a pipeline runs it must pull an image or tag from Docker Hub. Are you sure?',
this.group.dependencyProxyBlobCount,
),
{
count: this.group.dependencyProxyBlobCount,
},
);
},
showDeleteDropdown() {
return this.group.dependencyProxyBlobCount > 0;
},
},
methods: {
fetchNextPage() {
......@@ -103,13 +156,47 @@ export default {
},
});
},
async submit() {
try {
await Api.deleteDependencyProxyCacheList(this.groupId);
this.deleteCacheAlertMessage = this.$options.i18n.deleteCacheAlertMessageSuccess;
this.showDeleteCacheAlert = true;
} catch (err) {
this.deleteCacheAlertMessage = err;
this.showDeleteCacheAlert = true;
}
},
},
};
</script>
<template>
<div>
<title-area :title="$options.i18n.pageTitle" :info-messages="infoMessages" />
<gl-alert
v-if="showDeleteCacheAlert"
data-testid="delete-cache-alert"
@dismiss="showDeleteCacheAlert = false"
>
{{ deleteCacheAlertMessage }}
</gl-alert>
<title-area :title="$options.i18n.pageTitle" :info-messages="infoMessages">
<template v-if="showDeleteDropdown" #right-actions>
<gl-dropdown
icon="ellipsis_v"
text="More actions"
:text-sr-only="true"
category="tertiary"
no-caret
>
<gl-dropdown-item
v-gl-modal-directive="$options.confirmClearCacheModal"
variant="danger"
>{{ $options.i18n.clearCache }}</gl-dropdown-item
>
</gl-dropdown>
</template>
</title-area>
<gl-alert
v-if="!dependencyProxyAvailable"
:dismissible="false"
......@@ -159,5 +246,15 @@ export default {
:title="$options.i18n.noManifestTitle"
/>
</div>
<gl-modal
:modal-id="$options.confirmClearCacheModal"
:title="modalTitleWithCount"
:action-primary="$options.modalButtons.primary"
:action-secondary="$options.modalButtons.secondary"
@primary="submit"
>
{{ modalConfirmationMessageWithCount }}
</gl-modal>
</div>
</template>
......@@ -4,4 +4,4 @@
#js-dependency-proxy{ data: { group_path: @group.full_path,
dependency_proxy_available: dependency_proxy_available.to_s,
no_manifests_illustration: image_path('illustrations/docker-empty-state.svg') } }
no_manifests_illustration: image_path('illustrations/docker-empty-state.svg'), group_id: @group.id } }
......@@ -7621,6 +7621,11 @@ msgstr ""
msgid "Clear"
msgstr ""
msgid "Clear %{count} image from cache?"
msgid_plural "Clear %{count} images from cache?"
msgstr[0] ""
msgstr[1] ""
msgid "Clear all repository checks"
msgstr ""
......@@ -12155,9 +12160,15 @@ msgstr ""
msgid "Dependency list"
msgstr ""
msgid "DependencyProxy|All items in the cache are scheduled for removal."
msgstr ""
msgid "DependencyProxy|Cached %{time}"
msgstr ""
msgid "DependencyProxy|Clear cache"
msgstr ""
msgid "DependencyProxy|Clear the Dependency Proxy cache automatically"
msgstr ""
......@@ -42727,6 +42738,11 @@ msgstr ""
msgid "You are about to add %{usersTag} people to the discussion. They will all receive a notification."
msgstr ""
msgid "You are about to clear %{count} image from the cache. Once you confirm, the next time a pipeline runs it must pull an image or tag from Docker Hub. Are you sure?"
msgid_plural "You are about to clear %{count} images from the cache. Once you confirm, the next time a pipeline runs it must pull an image or tag from Docker Hub. Are you sure?"
msgstr[0] ""
msgstr[1] ""
msgid "You are about to delete this forked project containing:"
msgstr ""
......
......@@ -1671,6 +1671,18 @@ describe('Api', () => {
});
});
describe('dependency proxy cache', () => {
it('schedules the cache list for deletion', async () => {
const groupId = 1;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/dependency_proxy/cache`;
mock.onDelete(expectedUrl).reply(httpStatus.ACCEPTED);
const { status } = await Api.deleteDependencyProxyCacheList(groupId, {});
expect(status).toBe(httpStatus.ACCEPTED);
});
});
describe('Feature Flag User List', () => {
let expectedUrl;
let projectId;
......
import {
GlAlert,
GlDropdown,
GlDropdownItem,
GlFormInputGroup,
GlFormGroup,
GlModal,
GlSkeletonLoader,
GlSprintf,
GlEmptyState,
} from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import MockAdapter from 'axios-mock-adapter';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { stripTypenames } from 'helpers/graphql_helpers';
import waitForPromises from 'helpers/wait_for_promises';
import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants';
import axios from '~/lib/utils/axios_utils';
import DependencyProxyApp from '~/packages_and_registries/dependency_proxy/app.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ManifestsList from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue';
......@@ -21,13 +28,25 @@ import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency
import { proxyDetailsQuery, proxyData, pagination, proxyManifests } from './mock_data';
const dummyApiVersion = 'v3000';
const dummyGrouptId = 1;
const dummyUrlRoot = '/gitlab';
const dummyGon = {
api_version: dummyApiVersion,
relative_url_root: dummyUrlRoot,
};
let originalGon;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${dummyGrouptId}/dependency_proxy/cache`;
describe('DependencyProxyApp', () => {
let wrapper;
let apolloProvider;
let resolver;
let mock;
const provideDefaults = {
groupPath: 'gitlab-org',
groupId: dummyGrouptId,
dependencyProxyAvailable: true,
noManifestsIllustration: 'noManifestsIllustration',
};
......@@ -43,9 +62,14 @@ describe('DependencyProxyApp', () => {
apolloProvider,
provide,
stubs: {
GlAlert,
GlDropdown,
GlDropdownItem,
GlFormInputGroup,
GlFormGroup,
GlModal,
GlSprintf,
TitleArea,
},
});
}
......@@ -59,13 +83,24 @@ describe('DependencyProxyApp', () => {
const findProxyCountText = () => wrapper.findByTestId('proxy-count');
const findManifestList = () => wrapper.findComponent(ManifestsList);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findClearCacheDropdownList = () => wrapper.findComponent(GlDropdown);
const findClearCacheModal = () => wrapper.findComponent(GlModal);
const findClearCacheAlert = () => wrapper.findComponent(GlAlert);
beforeEach(() => {
resolver = jest.fn().mockResolvedValue(proxyDetailsQuery());
originalGon = window.gon;
window.gon = { ...dummyGon };
mock = new MockAdapter(axios);
mock.onDelete(expectedUrl).reply(202, {});
});
afterEach(() => {
wrapper.destroy();
window.gon = originalGon;
mock.restore();
});
describe('when the dependency proxy is not available', () => {
......@@ -95,6 +130,12 @@ describe('DependencyProxyApp', () => {
expect(resolver).not.toHaveBeenCalled();
});
it('hides the clear cache dropdown list', () => {
createComponent(createComponentArguments);
expect(findClearCacheDropdownList().exists()).toBe(false);
});
});
describe('when the dependency proxy is available', () => {
......@@ -165,6 +206,7 @@ describe('DependencyProxyApp', () => {
}),
);
createComponent();
return waitForPromises();
});
......@@ -214,6 +256,28 @@ describe('DependencyProxyApp', () => {
fullPath: provideDefaults.groupPath,
});
});
it('shows the clear cache dropdown list', () => {
expect(findClearCacheDropdownList().exists()).toBe(true);
});
it('shows the clear cache confirmation modal', () => {
const modal = findClearCacheModal();
expect(modal.find('.modal-title').text()).toContain('Clear 2 images from cache?');
expect(modal.props('actionPrimary').text).toBe('Clear cache');
});
it('submits the clear cache request', async () => {
findClearCacheModal().vm.$emit('primary', { preventDefault: jest.fn() });
await waitForPromises();
expect(findClearCacheAlert().exists()).toBe(true);
expect(findClearCacheAlert().text()).toBe(
'All items in the cache are scheduled for removal.',
);
});
});
});
});
......
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