Commit ad8202ce authored by Zack Cuddy's avatar Zack Cuddy

Geo Package Files - Store Actions

This splits off from:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32872

This is an attempt at MVC to avoid a
massive MR.

This commit implements the GraphQL store actions.

This commit does not hook up the action to the UI.

This will happen as part of:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33522
parent 95ef9e5a
fragment PageInfo on PageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query($before: String!, $after: String!) {
geoNode {
packageFileRegistries(first: 20, before: $before, after: $after) {
pageInfo {
...PageInfo
}
edges {
cursor
node {
id
packageFileId
state
retryCount
lastSyncFailure
retryAt
lastSyncedAt
createdAt
}
}
}
}
}
......@@ -7,6 +7,8 @@ import {
normalizeHeaders,
convertObjectPropsToCamelCase,
} from '~/lib/utils/common_utils';
import packageFilesQuery from '../graphql/package_files.query.graphql';
import { gqClient } from '../utils';
import * as types from './mutation_types';
import { FILTER_STATES } from './constants';
......@@ -26,6 +28,28 @@ export const receiveReplicableItemsError = ({ state, commit }) => {
export const fetchReplicableItems = ({ state, dispatch }) => {
dispatch('requestReplicableItems');
return state.useGraphQl
? dispatch('fetchReplicableItemsGraphQl')
: dispatch('fetchReplicableItemsRestful');
};
export const fetchReplicableItemsGraphQl = ({ dispatch }) => {
gqClient
.query({
query: packageFilesQuery,
})
.then(res => {
const registries = res.data.geoNode.packageFileRegistries;
const data = registries.edges.map(e => e.node);
dispatch('receiveReplicableItemsSuccess', { data });
})
.catch(() => {
dispatch('receiveReplicableItemsError');
});
};
export const fetchReplicableItemsRestful = ({ state, dispatch }) => {
const { filterOptions, currentFilterIndex, currentPage, searchFilter } = state;
const statusFilter = currentFilterIndex ? filterOptions[currentFilterIndex] : filterOptions[0];
......
......@@ -2,6 +2,7 @@ import { FILTER_STATES } from './constants';
const createState = replicableType => ({
replicableType,
useGraphQl: false,
isLoading: false,
replicableItems: [],
......
import createGqClient, { fetchPolicies } from '~/lib/graphql';
/*
This file uses a NO_CACHE policy due to the need for Geo data to always be fresh.
The UI this serves is used to watch the "syncing" process of items and their statuses
will need to be constantly re-queried as the user navigates around to not mistakenly
think the sync process is broken.
*/
// eslint-disable-next-line import/prefer-default-export
export const gqClient = createGqClient(
{},
{
fetchPolicy: fetchPolicies.NO_CACHE,
},
);
......@@ -89,8 +89,9 @@ describe('GeoReplicableApp', () => {
describe('with replicableItems', () => {
beforeEach(() => {
wrapper.vm.$store.state.replicableItems = MOCK_BASIC_FETCH_DATA_MAP.data;
wrapper.vm.$store.state.totalReplicableItems = MOCK_BASIC_FETCH_DATA_MAP.total;
wrapper.vm.$store.state.replicableItems = MOCK_BASIC_FETCH_DATA_MAP;
wrapper.vm.$store.state.totalReplicableItems =
wrapper.vm.$store.state.replicableItems.length;
});
it('shows replicable items', () => {
......
......@@ -11,7 +11,7 @@ localVue.use(Vuex);
describe('GeoReplicableItem', () => {
let wrapper;
const mockReplicable = MOCK_BASIC_FETCH_DATA_MAP.data[0];
const mockReplicable = MOCK_BASIC_FETCH_DATA_MAP[0];
const actionSpies = {
initiateReplicableSync: jest.fn(),
......
......@@ -70,7 +70,7 @@ describe('GeoReplicable', () => {
describe('GeoReplicableItem', () => {
beforeEach(() => {
wrapper.vm.$store.state.replicableItems = MOCK_BASIC_FETCH_DATA_MAP.data;
wrapper.vm.$store.state.replicableItems = MOCK_BASIC_FETCH_DATA_MAP;
});
it('renders an instance for each replicableItem in the store', () => {
......
......@@ -30,12 +30,51 @@ export const MOCK_BASIC_FETCH_RESPONSE = {
},
};
export const MOCK_BASIC_FETCH_DATA_MAP = {
data: convertObjectPropsToCamelCase(MOCK_BASIC_FETCH_RESPONSE.data, { deep: true }),
export const MOCK_BASIC_FETCH_DATA_MAP = convertObjectPropsToCamelCase(
MOCK_BASIC_FETCH_RESPONSE.data,
{ deep: true },
);
export const MOCK_RESTFUL_PAGINATION_DATA = {
perPage: MOCK_BASIC_FETCH_RESPONSE.headers['x-per-page'],
total: MOCK_BASIC_FETCH_RESPONSE.headers['x-total'],
};
export const MOCK_GRAPHQL_PAGINATION_DATA = {
hasNextPage: true,
hasPreviousPage: true,
startCursor: 'abc123',
endCursor: 'abc124',
};
export const MOCK_BASIC_GRAPHQL_QUERY_RESPONSE = {
geoNode: {
packageFileRegistries: {
pageInfo: MOCK_GRAPHQL_PAGINATION_DATA,
edges: [
{
cursor: 'abc123',
node: {
id: 'git/1',
packageFileId: '1',
state: 'PENDING',
lastSyncedAt: null,
},
},
{
cursor: 'abc124',
node: {
id: 'git/2',
packageFileId: '2',
state: 'SYNCED',
lastSyncedAt: null,
},
},
],
},
},
};
export const MOCK_BASIC_POST_RESPONSE = {
status: 'ok',
};
import testAction from 'helpers/vuex_action_helper';
import flash from '~/flash';
import toast from '~/vue_shared/plugins/global_toast';
import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
import Api from 'ee/api';
import * as actions from 'ee/geo_replicable/store/actions';
import * as types from 'ee/geo_replicable/store/mutation_types';
import createState from 'ee/geo_replicable/store/state';
import { ACTION_TYPES } from 'ee/geo_replicable/store/constants';
import { gqClient } from 'ee/geo_replicable/utils';
import packageFilesQuery from 'ee/geo_replicable/graphql/package_files.query.graphql';
import {
MOCK_BASIC_FETCH_DATA_MAP,
MOCK_BASIC_FETCH_RESPONSE,
MOCK_BASIC_POST_RESPONSE,
MOCK_REPLICABLE_TYPE,
MOCK_RESTFUL_PAGINATION_DATA,
MOCK_BASIC_GRAPHQL_QUERY_RESPONSE,
} from '../mock_data';
jest.mock('~/flash');
......@@ -40,9 +45,14 @@ describe('GeoReplicable Store Actions', () => {
it('should commit mutation RECEIVE_REPLICABLE_ITEMS_SUCCESS', done => {
testAction(
actions.receiveReplicableItemsSuccess,
MOCK_BASIC_FETCH_DATA_MAP,
{ data: MOCK_BASIC_FETCH_DATA_MAP, pagination: MOCK_RESTFUL_PAGINATION_DATA },
state,
[{ type: types.RECEIVE_REPLICABLE_ITEMS_SUCCESS, payload: MOCK_BASIC_FETCH_DATA_MAP }],
[
{
type: types.RECEIVE_REPLICABLE_ITEMS_SUCCESS,
payload: { data: MOCK_BASIC_FETCH_DATA_MAP, pagination: MOCK_RESTFUL_PAGINATION_DATA },
},
],
[],
done,
);
......@@ -65,6 +75,95 @@ describe('GeoReplicable Store Actions', () => {
});
describe('fetchReplicableItems', () => {
describe('with graphql', () => {
beforeEach(() => {
state.useGraphQl = true;
});
it('calls fetchReplicableItemsGraphQl', done => {
testAction(
actions.fetchReplicableItems,
null,
state,
[],
[{ type: 'requestReplicableItems' }, { type: 'fetchReplicableItemsGraphQl' }],
done,
);
});
});
describe('without graphql', () => {
beforeEach(() => {
state.useGraphQl = false;
});
it('calls fetchReplicableItemsRestful', done => {
testAction(
actions.fetchReplicableItems,
null,
state,
[],
[{ type: 'requestReplicableItems' }, { type: 'fetchReplicableItemsRestful' }],
done,
);
});
});
});
describe('fetchReplicableItemsGraphQl', () => {
describe('on success', () => {
const registries = MOCK_BASIC_GRAPHQL_QUERY_RESPONSE.geoNode?.packageFileRegistries;
const data = registries.edges.map(e => e.node);
beforeEach(() => {
jest.spyOn(gqClient, 'query').mockResolvedValue({
data: MOCK_BASIC_GRAPHQL_QUERY_RESPONSE,
});
});
it('should call gqClient with no before/after variables', () => {
testAction(
actions.fetchReplicableItemsGraphQl,
null,
state,
[],
[
{
type: 'receiveReplicableItemsSuccess',
payload: { data },
},
],
() => {
expect(gqClient.query).toHaveBeenCalledWith({
query: packageFilesQuery,
});
},
);
});
});
describe('on error', () => {
beforeEach(() => {
jest.spyOn(gqClient, 'query').mockRejectedValue();
});
it('should dispatch the request and error actions', done => {
testAction(
actions.fetchReplicableItemsGraphQl,
null,
state,
[],
[{ type: 'receiveReplicableItemsError' }],
done,
);
});
});
});
describe('fetchReplicableItemsRestful', () => {
const normalizedHeaders = normalizeHeaders(MOCK_BASIC_FETCH_RESPONSE.headers);
const pagination = parseIntPagination(normalizedHeaders);
describe('on success', () => {
beforeEach(() => {
jest.spyOn(Api, 'getGeoReplicableItems').mockResolvedValue(MOCK_BASIC_FETCH_RESPONSE);
......@@ -79,13 +178,19 @@ describe('GeoReplicable Store Actions', () => {
it('should call getGeoReplicableItems with default queryParams', () => {
testAction(
actions.fetchReplicableItems,
actions.fetchReplicableItemsRestful,
{},
state,
[],
[
{ type: 'requestReplicableItems' },
{ type: 'receiveReplicableItemsSuccess', payload: MOCK_BASIC_FETCH_DATA_MAP },
{
type: 'receiveReplicableItemsSuccess',
payload: {
data: MOCK_BASIC_FETCH_DATA_MAP,
perPage: pagination.perPage,
total: pagination.total,
},
},
],
() => {
expect(Api.getGeoReplicableItems).toHaveBeenCalledWith(
......@@ -106,13 +211,19 @@ describe('GeoReplicable Store Actions', () => {
it('should call getGeoReplicableItems with default queryParams', () => {
testAction(
actions.fetchReplicableItems,
actions.fetchReplicableItemsRestful,
{},
state,
[],
[
{ type: 'requestReplicableItems' },
{ type: 'receiveReplicableItemsSuccess', payload: MOCK_BASIC_FETCH_DATA_MAP },
{
type: 'receiveReplicableItemsSuccess',
payload: {
data: MOCK_BASIC_FETCH_DATA_MAP,
perPage: pagination.perPage,
total: pagination.total,
},
},
],
() => {
expect(Api.getGeoReplicableItems).toHaveBeenCalledWith(MOCK_REPLICABLE_TYPE, {
......@@ -133,11 +244,11 @@ describe('GeoReplicable Store Actions', () => {
it('should dispatch the request and error actions', done => {
testAction(
actions.fetchReplicableItems,
actions.fetchReplicableItemsRestful,
{},
state,
[],
[{ type: 'requestReplicableItems' }, { type: 'receiveReplicableItemsError' }],
[{ type: 'receiveReplicableItemsError' }],
done,
);
});
......
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