Commit 3d59ceb2 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera Committed by Natalia Tepluhina

Enable delete of package in refactored package details

parent 4de44833
...@@ -39,7 +39,9 @@ import { ...@@ -39,7 +39,9 @@ import {
CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION, CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION,
SHOW_DELETE_SUCCESS_ALERT, SHOW_DELETE_SUCCESS_ALERT,
FETCH_PACKAGE_DETAILS_ERROR_MESSAGE, FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
DELETE_PACKAGE_ERROR_MESSAGE,
} from '~/packages_and_registries/package_registry/constants'; } from '~/packages_and_registries/package_registry/constants';
import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql';
import getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql'; import getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
...@@ -156,19 +158,42 @@ export default { ...@@ -156,19 +158,42 @@ export default {
// this.fetchPackageVersions(); // this.fetchPackageVersions();
} }
}, },
deletePackage() {
return this.$apollo
.mutate({
mutation: destroyPackageMutation,
variables: {
id: this.packageEntity.id,
},
})
.then(({ data }) => {
if (data?.destroyPackage?.errors[0]) {
throw data.destroyPackage.errors[0];
}
});
},
async confirmPackageDeletion() { async confirmPackageDeletion() {
this.track(DELETE_PACKAGE_TRACKING_ACTION); this.track(DELETE_PACKAGE_TRACKING_ACTION);
await this.deletePackage(); try {
await this.deletePackage();
const returnTo = const returnTo =
!this.groupListUrl || document.referrer.includes(this.projectName) !this.groupListUrl || document.referrer.includes(this.projectName)
? this.projectListUrl ? this.projectListUrl
: this.groupListUrl; // to avoid security issue url are supplied from backend : this.groupListUrl; // to avoid security issue url are supplied from backend
const modalQuery = objectToQuery({ [SHOW_DELETE_SUCCESS_ALERT]: true }); const modalQuery = objectToQuery({ [SHOW_DELETE_SUCCESS_ALERT]: true });
window.location.replace(`${returnTo}?${modalQuery}`); window.location.replace(`${returnTo}?${modalQuery}`);
} catch (error) {
createFlash({
message: DELETE_PACKAGE_ERROR_MESSAGE,
type: 'warning',
captureError: true,
error,
});
}
}, },
handleFileDelete(file) { handleFileDelete(file) {
this.track(REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION); this.track(REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION);
...@@ -225,10 +250,10 @@ export default { ...@@ -225,10 +250,10 @@ export default {
<gl-button <gl-button
v-if="canDelete" v-if="canDelete"
v-gl-modal="'delete-modal'" v-gl-modal="'delete-modal'"
class="js-delete-button"
variant="danger" variant="danger"
category="primary" category="primary"
data-qa-selector="delete_button" data-qa-selector="delete_button"
data-testid="delete-package"
> >
{{ __('Delete') }} {{ __('Delete') }}
</gl-button> </gl-button>
...@@ -303,7 +328,6 @@ export default { ...@@ -303,7 +328,6 @@ export default {
<gl-modal <gl-modal
ref="deleteModal" ref="deleteModal"
class="js-delete-modal"
modal-id="delete-modal" modal-id="delete-modal"
:action-primary="$options.modal.packageDeletePrimaryAction" :action-primary="$options.modal.packageDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction" :action-cancel="$options.modal.cancelAction"
......
mutation destroyPackage($id: PackagesPackageID!) {
destroyPackage(input: { id: $id }) {
errors
}
}
import { GlEmptyState } from '@gitlab/ui'; import { GlEmptyState, GlModal } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash'; import createFlash from '~/flash';
...@@ -10,11 +12,22 @@ import PackagesApp from '~/packages_and_registries/package_registry/components/d ...@@ -10,11 +12,22 @@ import PackagesApp from '~/packages_and_registries/package_registry/components/d
import InstallationCommands from '~/packages_and_registries/package_registry/components/details/installation_commands.vue'; import InstallationCommands from '~/packages_and_registries/package_registry/components/details/installation_commands.vue';
import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue'; import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue';
import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue'; import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue';
import { FETCH_PACKAGE_DETAILS_ERROR_MESSAGE } from '~/packages_and_registries/package_registry/constants'; import {
FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
DELETE_PACKAGE_ERROR_MESSAGE,
} from '~/packages_and_registries/package_registry/constants';
import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql';
import getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql'; import getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql';
import { packageDetailsQuery, packageData, emptyPackageDetailsQuery } from '../../mock_data'; import {
packageDetailsQuery,
packageData,
emptyPackageDetailsQuery,
packageDestroyMutation,
packageDestroyMutationError,
} from '../../mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
useMockLocationHelper();
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -34,16 +47,23 @@ describe('PackagesApp', () => { ...@@ -34,16 +47,23 @@ describe('PackagesApp', () => {
groupListUrl: 'groupListUrl', groupListUrl: 'groupListUrl',
}; };
function createComponent({ resolver = jest.fn().mockResolvedValue(packageDetailsQuery()) } = {}) { function createComponent({
resolver = jest.fn().mockResolvedValue(packageDetailsQuery()),
mutationResolver = jest.fn().mockResolvedValue(packageDestroyMutation()),
} = {}) {
localVue.use(VueApollo); localVue.use(VueApollo);
const requestHandlers = [[getPackageDetails, resolver]]; const requestHandlers = [
[getPackageDetails, resolver],
[destroyPackageMutation, mutationResolver],
];
apolloProvider = createMockApollo(requestHandlers); apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMount(PackagesApp, { wrapper = shallowMountExtended(PackagesApp, {
localVue, localVue,
apolloProvider, apolloProvider,
provide, provide,
stubs: { PackageTitle },
}); });
} }
...@@ -52,6 +72,8 @@ describe('PackagesApp', () => { ...@@ -52,6 +72,8 @@ describe('PackagesApp', () => {
const findPackageHistory = () => wrapper.findComponent(PackageHistory); const findPackageHistory = () => wrapper.findComponent(PackageHistory);
const findAdditionalMetadata = () => wrapper.findComponent(AdditionalMetadata); const findAdditionalMetadata = () => wrapper.findComponent(AdditionalMetadata);
const findInstallationCommands = () => wrapper.findComponent(InstallationCommands); const findInstallationCommands = () => wrapper.findComponent(InstallationCommands);
const findDeleteModal = () => wrapper.findComponent(GlModal);
const findDeleteButton = () => wrapper.findByTestId('delete-package');
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -121,4 +143,101 @@ describe('PackagesApp', () => { ...@@ -121,4 +143,101 @@ describe('PackagesApp', () => {
packageEntity: expect.objectContaining(packageData()), packageEntity: expect.objectContaining(packageData()),
}); });
}); });
describe('delete package', () => {
const originalReferrer = document.referrer;
const setReferrer = (value = provide.projectName) => {
Object.defineProperty(document, 'referrer', {
value,
configurable: true,
});
};
const performDeletePackage = async () => {
await findDeleteButton().trigger('click');
findDeleteModal().vm.$emit('primary');
await waitForPromises();
};
afterEach(() => {
Object.defineProperty(document, 'referrer', {
value: originalReferrer,
configurable: true,
});
});
it('shows the delete confirmation modal when delete is clicked', async () => {
createComponent();
await waitForPromises();
await findDeleteButton().trigger('click');
expect(findDeleteModal().exists()).toBe(true);
});
describe('successful request', () => {
it('when referrer contains project name calls window.replace with project url', async () => {
setReferrer();
createComponent();
await waitForPromises();
await performDeletePackage();
expect(window.location.replace).toHaveBeenCalledWith(
'projectListUrl?showSuccessDeleteAlert=true',
);
});
it('when referrer does not contain project name calls window.replace with group url', async () => {
setReferrer('baz');
createComponent();
await waitForPromises();
await performDeletePackage();
expect(window.location.replace).toHaveBeenCalledWith(
'groupListUrl?showSuccessDeleteAlert=true',
);
});
});
describe('request failure', () => {
it('on global failure it displays an alert', async () => {
createComponent({ mutationResolver: jest.fn().mockRejectedValue() });
await waitForPromises();
await performDeletePackage();
expect(createFlash).toHaveBeenCalledWith(
expect.objectContaining({
message: DELETE_PACKAGE_ERROR_MESSAGE,
}),
);
});
it('on payload with error it displays an alert', async () => {
createComponent({
mutationResolver: jest.fn().mockResolvedValue(packageDestroyMutationError()),
});
await waitForPromises();
await performDeletePackage();
expect(createFlash).toHaveBeenCalledWith(
expect.objectContaining({
message: DELETE_PACKAGE_ERROR_MESSAGE,
}),
);
});
});
});
}); });
...@@ -125,3 +125,29 @@ export const emptyPackageDetailsQuery = () => ({ ...@@ -125,3 +125,29 @@ export const emptyPackageDetailsQuery = () => ({
}, },
}, },
}); });
export const packageDestroyMutation = () => ({
data: {
destroyPackage: {
errors: [],
},
},
});
export const packageDestroyMutationError = () => ({
data: {
destroyPackage: null,
},
errors: [
{
message:
"The resource that you are attempting to access does not exist or you don't have permission to perform this action",
locations: [
{
line: 2,
column: 3,
},
],
path: ['destroyPackage'],
},
],
});
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