Commit 34949266 authored by David O'Regan's avatar David O'Regan

Merge branch 'nmezzopera-refactor-image-details-fetch' into 'master'

Add supporting code to implement fetching images details with API

See merge request gitlab-org/gitlab!46794
parents a2369565 3c8b2098
......@@ -70,6 +70,7 @@ const Api = {
featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists',
featureFlagUserList: '/api/:version/projects/:id/feature_flags_user_lists/:list_iid',
billableGroupMembersPath: '/api/:version/groups/:id/billable_members',
containerRegistryDetailsPath: '/api/:version/registry/repositories/:id/',
group(groupId, callback = () => {}) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
......@@ -106,6 +107,11 @@ const Api = {
return axios.delete(url);
},
containerRegistryDetails(registryId, options = {}) {
const url = Api.buildUrl(this.containerRegistryDetailsPath).replace(':id', registryId);
return axios.get(url, options);
},
groupMembers(id, options) {
const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id));
......
......@@ -15,6 +15,10 @@ export const DELETE_TAGS_SUCCESS_MESSAGE = s__(
'ContainerRegistry|Tags successfully marked for deletion.',
);
export const FETCH_IMAGE_DETAILS_ERROR_MESSAGE = s__(
'ContainerRegistry|Something went wrong while fetching the image details.',
);
export const TAGS_LIST_TITLE = s__('ContainerRegistry|Image tags');
export const DIGEST_LABEL = s__('ContainerRegistry|Digest: %{imageId}');
export const CREATED_AT_LABEL = s__('ContainerRegistry|Published %{timeInfo}');
......
import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import Api from '~/api';
import * as types from './mutation_types';
import {
FETCH_IMAGES_LIST_ERROR_MESSAGE,
DEFAULT_PAGE,
DEFAULT_PAGE_SIZE,
FETCH_TAGS_LIST_ERROR_MESSAGE,
FETCH_IMAGE_DETAILS_ERROR_MESSAGE,
} from '../constants/index';
import { decodeAndParse } from '../utils';
......@@ -61,6 +63,19 @@ export const requestTagsList = ({ commit, dispatch }, { pagination = {}, params
});
};
export const requestImageDetailsAndTagsList = ({ dispatch, commit }, id) => {
commit(types.SET_MAIN_LOADING, true);
return Api.containerRegistryDetails(id)
.then(({ data }) => {
commit(types.SET_IMAGE_DETAILS, data);
dispatch('requestTagsList');
})
.catch(() => {
createFlash(FETCH_IMAGE_DETAILS_ERROR_MESSAGE);
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestDeleteTag = ({ commit, dispatch, state }, { tag, params }) => {
commit(types.SET_MAIN_LOADING, true);
return axios
......
......@@ -7,3 +7,4 @@ export const SET_MAIN_LOADING = 'SET_MAIN_LOADING';
export const SET_TAGS_PAGINATION = 'SET_TAGS_PAGINATION';
export const SET_TAGS_LIST_SUCCESS = 'SET_TAGS_LIST_SUCCESS';
export const SET_SHOW_GARBAGE_COLLECTION_TIP = 'SET_SHOW_GARBAGE_COLLECTION_TIP';
export const SET_IMAGE_DETAILS = 'SET_IMAGE_DETAILS';
......@@ -47,4 +47,8 @@ export default {
const normalizedHeaders = normalizeHeaders(headers);
state.tagsPagination = parseIntPagination(normalizedHeaders);
},
[types.SET_IMAGE_DETAILS](state, details) {
state.imageDetails = details;
},
};
......@@ -3,6 +3,7 @@ export default () => ({
showGarbageCollectionTip: false,
config: {},
images: [],
imageDetails: {},
tags: [],
pagination: {},
tagsPagination: {},
......
export const decodeAndParse = param => JSON.parse(window.atob(param));
// eslint-disable-next-line @gitlab/require-i18n-strings
export const pathGenerator = (imageDetails, ending = 'tags?format=json') => {
// this method is a temporary workaround, to be removed with graphql implementation
// https://gitlab.com/gitlab-org/gitlab/-/issues/276432
const basePath = imageDetails.path.replace(`/${imageDetails.name}`, '');
return `/${basePath}/registry/repository/${imageDetails.id}/${ending}`;
};
......@@ -7238,6 +7238,9 @@ msgstr ""
msgid "ContainerRegistry|Something went wrong while fetching the cleanup policy."
msgstr ""
msgid "ContainerRegistry|Something went wrong while fetching the image details."
msgstr ""
msgid "ContainerRegistry|Something went wrong while fetching the repository list."
msgstr ""
......
......@@ -118,6 +118,24 @@ describe('Api', () => {
});
});
describe('container registry', () => {
describe('containerRegistryDetails', () => {
it('fetch container registry details', async () => {
const expectedUrl = `foo`;
const apiResponse = {};
jest.spyOn(axios, 'get');
jest.spyOn(Api, 'buildUrl').mockReturnValueOnce(expectedUrl);
mock.onGet(expectedUrl).replyOnce(httpStatus.OK, apiResponse);
const { data } = await Api.containerRegistryDetails(1);
expect(data).toEqual(apiResponse);
expect(axios.get).toHaveBeenCalledWith(expectedUrl, {});
});
});
});
describe('group', () => {
it('fetches a group', done => {
const groupId = '123456';
......
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'helpers/test_constants';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import Api from '~/api';
import axios from '~/lib/utils/axios_utils';
import * as actions from '~/registry/explorer/stores/actions';
import * as types from '~/registry/explorer/stores/mutation_types';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { reposServerResponse, registryServerResponse } from '../mock_data';
jest.mock('~/flash.js');
......@@ -227,6 +228,47 @@ describe('Actions RegistryExplorer Store', () => {
});
});
describe('requestImageDetailsAndTagsList', () => {
it('sets the imageDetails and dispatch requestTagsList', done => {
const resolvedValue = { foo: 'bar' };
jest.spyOn(Api, 'containerRegistryDetails').mockResolvedValue({ data: resolvedValue });
testAction(
actions.requestImageDetailsAndTagsList,
1,
{},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_IMAGE_DETAILS, payload: resolvedValue },
],
[
{
type: 'requestTagsList',
},
],
done,
);
});
it('should create flash on error', done => {
jest.spyOn(Api, 'containerRegistryDetails').mockRejectedValue();
testAction(
actions.requestImageDetailsAndTagsList,
1,
{},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
() => {
expect(createFlash).toHaveBeenCalled();
done();
},
);
});
});
describe('request delete multiple tags', () => {
const url = `project-path/registry/repository/foo/tags`;
const params = window.btoa(JSON.stringify({ tags_path: `${url}?format=json` }));
......
......@@ -121,4 +121,13 @@ describe('Mutations Registry Explorer Store', () => {
expect(mockState).toEqual(expectedState);
});
});
describe('SET_IMAGE_DETAILS', () => {
it('should set imageDetails', () => {
const expectedState = { ...mockState, imageDetails: { foo: 'bar' } };
mutations[types.SET_IMAGE_DETAILS](mockState, { foo: 'bar' });
expect(mockState).toEqual(expectedState);
});
});
});
import { pathGenerator } from '~/registry/explorer/utils';
describe('Utils', () => {
describe('pathGenerator', () => {
const imageDetails = {
path: 'foo/bar/baz',
name: 'baz',
id: 1,
};
it('returns the fetch url when no ending is passed', () => {
expect(pathGenerator(imageDetails)).toBe('/foo/bar/registry/repository/1/tags?format=json');
});
it('returns the url with an ending when is passed', () => {
expect(pathGenerator(imageDetails, 'foo')).toBe('/foo/bar/registry/repository/1/foo');
});
});
});
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