Commit f44feb3a authored by Paul Slaughter's avatar Paul Slaughter

Merge branch '33905-connect-paths-from-api' into 'master'

Connect vue app to api _links

See merge request gitlab-org/gitlab!22096
parents 152d1286 e2ce61ae
......@@ -46,7 +46,6 @@ export default {
perPage: state => state.pagination.perPage,
totalItems: state => state.pagination.total,
page: state => state.pagination.page,
canDestroyPackage: state => state.config.canDestroyPackage,
isGroupPage: state => state.config.isGroupPage,
}),
currentPage: {
......@@ -75,7 +74,7 @@ export default {
return !this.list || this.list.length === 0;
},
showActions() {
return this.canDestroyPackage;
return !this.isGroupPage;
},
sortableFields() {
// This list is filtered in the case of the project page, and the project column is removed
......@@ -151,7 +150,7 @@ export default {
this.$refs.packageListDeleteModal.show();
},
deleteItemConfirmation() {
this.$emit('package:delete', this.itemToBeDeleted.id);
this.$emit('package:delete', this.itemToBeDeleted);
this.track(TrackingActions.DELETE_PACKAGE);
this.itemToBeDeleted = null;
},
......@@ -191,9 +190,13 @@ export default {
:no-local-sorting="true"
stacked="md"
>
<template #name="{value}">
<template #name="{value, item}">
<div ref="col-name" class="flex-truncate-parent">
<a href="#" class="flex-truncate-child" data-qa-selector="package_link">
<a
:href="item._links.web_path"
class="flex-truncate-child"
data-qa-selector="package_link"
>
{{ value }}
</a>
</div>
......@@ -219,6 +222,7 @@ export default {
variant="danger"
:title="s__('PackageRegistry|Remove package')"
:aria-label="s__('PackageRegistry|Remove package')"
:disabled="!item._links.delete_api_path"
@click="setItemToBeDeleted(item)"
>
<icon name="remove" />
......
......@@ -38,8 +38,8 @@ export default {
onPageChanged(page) {
return this.requestPackagesList({ page });
},
onPackageDeleteRequest(packageId) {
return this.requestDeletePackage({ projectId: this.resourceId, packageId });
onPackageDeleteRequest(item) {
return this.requestDeletePackage(item);
},
},
};
......
......@@ -25,3 +25,6 @@ export const LIST_LABEL_VERSION = __('Version');
export const LIST_LABEL_PACKAGE_TYPE = __('Type');
export const LIST_LABEL_CREATED_AT = __('Created');
export const LIST_LABEL_ACTIONS = '';
// The following is not translated because it is used to build a JavaScript exception error message
export const MISSING_DELETE_PATH_ERROR = 'Missing delete_api_path link';
import Api from 'ee/api';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import * as types from './mutation_types';
import {
......@@ -7,6 +8,7 @@ import {
DELETE_PACKAGE_SUCCESS_MESSAGE,
DEFAULT_PAGE,
DEFAULT_PAGE_SIZE,
MISSING_DELETE_PATH_ERROR,
} from '../constants';
export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data);
......@@ -34,9 +36,16 @@ export const requestPackagesList = ({ dispatch, state }, pagination = {}) => {
});
};
export const requestDeletePackage = ({ dispatch }, { projectId, packageId }) => {
export const requestDeletePackage = ({ dispatch }, { _links }) => {
if (!_links || !_links.delete_api_path) {
createFlash(DELETE_PACKAGE_ERROR_MESSAGE);
const error = new Error(MISSING_DELETE_PATH_ERROR);
return Promise.reject(error);
}
dispatch('setLoading', true);
return Api.deleteProjectPackage(projectId, packageId)
return axios
.delete(_links.delete_api_path)
.then(() => {
dispatch('requestPackagesList');
createFlash(DELETE_PACKAGE_SUCCESS_MESSAGE, 'success');
......
import _ from 'underscore';
import * as types from './mutation_types';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import { GROUP_PAGE_TYPE } from '../constants';
......@@ -8,9 +7,6 @@ export default {
state.config = {
...config,
isGroupPage: config.pageType === GROUP_PAGE_TYPE,
canDestroyPackage: !(
_.isNull(config.canDestroyPackage) || _.isUndefined(config.canDestroyPackage)
),
};
},
......
......@@ -8,7 +8,6 @@ export default () => ({
* {
* resourceId: String,
* pageType: String,
* userCanDelete: Boolean,
* emptyListIllustration: String,
* emptyListHelpUrl: String
* }
......
......@@ -6,7 +6,6 @@
.col-12
#js-vue-packages-list{ data: { resource_id: @project.id,
page_type: 'projects',
can_destroy_package: can_destroy_package,
empty_list_help_url: help_page_path('administration/packages/index'),
empty_list_illustration: image_path('illustrations/no-packages.svg') } }
......
......@@ -77,10 +77,7 @@ describe('packages_list_app', () => {
it('call requestDeletePackage on package:delete', () => {
const list = findListComponent();
list.vm.$emit('package:delete', 1);
expect(componentConfig.methods.requestDeletePackage).toHaveBeenCalledWith({
projectId: 'project_id',
packageId: 1,
});
list.vm.$emit('package:delete', 'foo');
expect(componentConfig.methods.requestDeletePackage).toHaveBeenCalledWith('foo');
});
});
......@@ -53,7 +53,6 @@ describe('packages_list', () => {
...mountOptions,
computed: {
...mountOptions.computed,
canDestroyPackage: () => false,
isGroupPage: () => true,
},
});
......@@ -63,6 +62,11 @@ describe('packages_list', () => {
const projectColumn = findFirstProjectColumn();
expect(projectColumn.exists()).toBe(true);
});
it('does not show the action column', () => {
const action = findFirstActionColumn();
expect(action.exists()).toBe(false);
});
});
it('contains a sorting component', () => {
......@@ -84,20 +88,6 @@ describe('packages_list', () => {
expect(sorting.exists()).toBe(true);
});
describe('when user can not destroy the package', () => {
beforeEach(() => {
wrapper = mount(PackagesList, {
...mountOptions,
computed: { ...mountOptions.computed, canDestroyPackage: () => false },
});
});
it('does not show the action column', () => {
const action = findFirstActionColumn();
expect(action.exists()).toBe(false);
});
});
describe('when the user can destroy the package', () => {
it('show the action column', () => {
const action = findFirstActionColumn();
......@@ -131,10 +121,11 @@ describe('packages_list', () => {
});
it('deleteItemConfirmation emit package:delete', () => {
wrapper.setData({ itemToBeDeleted: { id: 2 } });
const itemToBeDeleted = { id: 2 };
wrapper.setData({ itemToBeDeleted });
wrapper.vm.deleteItemConfirmation();
return wrapper.vm.$nextTick(() => {
expect(wrapper.emitted('package:delete')).toEqual([[2]]);
expect(wrapper.emitted('package:delete')[0]).toEqual([itemToBeDeleted]);
});
});
......
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import Api from 'ee/api';
import createFlash from '~/flash';
import * as actions from 'ee/packages/list/stores/actions';
import * as types from 'ee/packages/list/stores/mutation_types';
import {
MISSING_DELETE_PATH_ERROR,
DELETE_PACKAGE_ERROR_MESSAGE,
} from 'ee/packages/list/constants';
import testAction from 'helpers/vuex_action_helper';
import Api from 'ee/api';
import createFlash from '~/flash';
jest.mock('~/flash.js');
jest.mock('ee/api.js');
describe('Actions Package list store', () => {
const headers = 'bar';
let mock;
describe('requestPackagesList', () => {
beforeEach(() => {
Api.projectPackages = jest.fn().mockResolvedValue({ data: 'foo', headers });
Api.groupPackages = jest.fn().mockResolvedValue({ data: 'baz', headers });
});
beforeEach(() => {
Api.projectPackages = jest.fn().mockResolvedValue({ data: 'foo', headers });
Api.groupPackages = jest.fn().mockResolvedValue({ data: 'baz', headers });
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('requestPackagesList', () => {
it('should fetch the project packages list when isGroupPage is false', done => {
testAction(
actions.requestPackagesList,
......@@ -117,16 +129,18 @@ describe('Actions Package list store', () => {
});
describe('requestDeletePackage', () => {
it('should call deleteProjectPackage', done => {
Api.deleteProjectPackage = jest.fn().mockResolvedValue({ data: 'foo' });
const payload = {
_links: {
delete_api_path: 'foo',
},
};
it('should perform a delete operation on _links.delete_api_path', done => {
mock.onDelete(payload._links.delete_api_path).replyOnce(200);
Api.projectPackages = jest.fn().mockResolvedValue({ data: 'foo' });
testAction(
actions.requestDeletePackage,
{
projectId: 1,
packageId: 2,
},
payload,
null,
[],
[
......@@ -139,13 +153,10 @@ describe('Actions Package list store', () => {
});
it('should stop the loading and call create flash on api error', done => {
Api.deleteProjectPackage = jest.fn().mockRejectedValue();
mock.onDelete(payload._links.delete_api_path).replyOnce(400);
testAction(
actions.requestDeletePackage,
{
projectId: 1,
packageId: 2,
},
payload,
null,
[],
[{ type: 'setLoading', payload: true }, { type: 'setLoading', payload: false }],
......@@ -155,5 +166,17 @@ describe('Actions Package list store', () => {
},
);
});
it.each`
property | actionPayload
${'_links'} | ${{}}
${'delete_api_path'} | ${{ _links: {} }}
`('should reject and createFlash when $property is missing', ({ actionPayload }, done) => {
testAction(actions.requestDeletePackage, actionPayload, null, [], []).catch(e => {
expect(e).toEqual(new Error(MISSING_DELETE_PATH_ERROR));
expect(createFlash).toHaveBeenCalledWith(DELETE_PACKAGE_ERROR_MESSAGE);
done();
});
});
});
});
const _links = {
web_path: 'foo',
delete_api_path: 'bar',
};
export const mavenPackage = {
created_at: '2015-12-10',
id: 1,
......@@ -11,6 +16,7 @@ export const mavenPackage = {
project_id: 1,
updated_at: '2015-12-10',
version: '1.0.0',
_links,
};
export const mavenFiles = [
......@@ -38,6 +44,7 @@ export const npmPackage = {
project_id: 1,
updated_at: '2015-12-10',
version: '',
_links,
};
export const npmFiles = [
......@@ -64,6 +71,7 @@ export const conanPackage = {
recipe: 'conan-package/1.0.0@conan+conan-package/stable',
updated_at: '2015-12-10',
version: '1.0.0',
_links,
};
export const packageList = [mavenPackage, npmPackage, conanPackage];
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