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

Merge branch 'pipeline-editor/cache-new-branches' into 'master'

Cache newly committed branches in branch switcher

See merge request gitlab-org/gitlab!62872
parents 5bf41b99 a9967814
......@@ -8,6 +8,8 @@ import {
COMMIT_SUCCESS,
} from '../../constants';
import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql';
import updateCurrentBranchMutation from '../../graphql/mutations/update_current_branch.mutation.graphql';
import updateLastCommitBranchMutation from '../../graphql/mutations/update_last_commit_branch.mutation.graphql';
import getCommitSha from '../../graphql/queries/client/commit_sha.graphql';
import getCurrentBranch from '../../graphql/queries/client/current_branch.graphql';
import getIsNewCiConfigFile from '../../graphql/queries/client/is_new_ci_config_file.graphql';
......@@ -113,6 +115,8 @@ export default {
this.redirectToNewMergeRequest(targetBranch);
} else {
this.$emit('commit', { type: COMMIT_SUCCESS });
this.updateLastCommitBranch(targetBranch);
this.updateCurrentBranch(targetBranch);
}
} catch (error) {
this.$emit('showError', { type: COMMIT_FAILURE, reasons: [error?.message] });
......@@ -123,6 +127,18 @@ export default {
onCommitCancel() {
this.$emit('resetContent');
},
updateCurrentBranch(currentBranch) {
this.$apollo.mutate({
mutation: updateCurrentBranchMutation,
variables: { currentBranch },
});
},
updateLastCommitBranch(lastCommitBranch) {
this.$apollo.mutate({
mutation: updateLastCommitBranchMutation,
variables: { lastCommitBranch },
});
},
},
};
</script>
......
......@@ -18,8 +18,10 @@ import {
BRANCH_SEARCH_DEBOUNCE,
DEFAULT_FAILURE,
} from '~/pipeline_editor/constants';
import getAvailableBranches from '~/pipeline_editor/graphql/queries/available_branches.graphql';
import getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_branch.graphql';
import updateCurrentBranchMutation from '~/pipeline_editor/graphql/mutations/update_current_branch.mutation.graphql';
import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.graphql';
import getCurrentBranchQuery from '~/pipeline_editor/graphql/queries/client/current_branch.graphql';
import getLastCommitBranchQuery from '~/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql';
export default {
i18n: {
......@@ -55,11 +57,12 @@ export default {
pageLimit: this.paginationLimit,
pageCounter: 0,
searchTerm: '',
lastCommitBranch: '',
};
},
apollo: {
availableBranches: {
query: getAvailableBranches,
query: getAvailableBranchesQuery,
variables() {
return {
limit: this.paginationLimit,
......@@ -79,7 +82,16 @@ export default {
},
},
currentBranch: {
query: getCurrentBranch,
query: getCurrentBranchQuery,
},
lastCommitBranch: {
query: getLastCommitBranchQuery,
result({ data: { lastCommitBranch } }) {
if (lastCommitBranch === '' || this.availableBranches.includes(lastCommitBranch)) {
return;
}
this.availableBranches.unshift(lastCommitBranch);
},
},
},
computed: {
......@@ -94,13 +106,14 @@ export default {
},
},
methods: {
availableBranchesQueryVars() {
availableBranchesQueryVars(varsOverride = {}) {
if (this.searchTerm.length > 0) {
return {
limit: this.totalBranches,
offset: 0,
projectFullPath: this.projectFullPath,
searchPattern: `*${this.searchTerm}*`,
...varsOverride,
};
}
......@@ -109,6 +122,7 @@ export default {
offset: this.pageCounter * this.paginationLimit,
projectFullPath: this.projectFullPath,
searchPattern: '*',
...varsOverride,
};
},
// if there is no searchPattern, paginate by {paginationLimit} branches
......@@ -116,7 +130,7 @@ export default {
if (
this.isBranchesLoading ||
this.searchTerm.length > 0 ||
this.branches.length === this.totalBranches
this.branches.length >= this.totalBranches
) {
return;
}
......@@ -140,11 +154,7 @@ export default {
return;
}
await this.$apollo.getClient().writeQuery({
query: getCurrentBranch,
data: { currentBranch: newBranch },
});
this.updateCurrentBranch(newBranch);
const updatedPath = setUrlParams({ branch_name: newBranch });
historyPushState(updatedPath);
......@@ -162,7 +172,7 @@ export default {
this.isSearchingBranches = true;
const fetchResults = await this.$apollo
.query({
query: getAvailableBranches,
query: getAvailableBranchesQuery,
fetchPolicy: fetchPolicies.NETWORK_ONLY,
variables: this.availableBranchesQueryVars(),
})
......@@ -177,6 +187,12 @@ export default {
reasons: [this.$options.i18n.fetchError],
});
},
updateCurrentBranch(currentBranch) {
this.$apollo.mutate({
mutation: updateCurrentBranchMutation,
variables: { currentBranch },
});
},
},
};
</script>
......@@ -197,7 +213,6 @@ export default {
<gl-infinite-scroll
:fetched-items="branches.length"
:total-items="totalBranches"
:max-list-height="250"
@bottomReached="fetchNextBranches"
>
......
mutation updateCurrentBranch($currentBranch: String) {
updateCurrentBranch(currentBranch: $currentBranch) @client
}
mutation updateLastCommitBranch($lastCommitBranch: String) {
updateLastCommitBranch(lastCommitBranch: $lastCommitBranch) @client
}
import produce from 'immer';
import Api from '~/api';
import axios from '~/lib/utils/axios_utils';
import getCurrentBranchQuery from './queries/client/current_branch.graphql';
import getLastCommitBranchQuery from './queries/client/last_commit_branch.query.graphql';
export const resolvers = {
Query: {
......@@ -39,5 +42,21 @@ export const resolvers = {
__typename: 'CiLintContent',
}));
},
updateCurrentBranch: (_, { currentBranch = undefined }, { cache }) => {
cache.writeQuery({
query: getCurrentBranchQuery,
data: produce(cache.readQuery({ query: getCurrentBranchQuery }), (draftData) => {
draftData.currentBranch = currentBranch;
}),
});
},
updateLastCommitBranch: (_, { lastCommitBranch = undefined }, { cache }) => {
cache.writeQuery({
query: getLastCommitBranchQuery,
data: produce(cache.readQuery({ query: getLastCommitBranchQuery }), (draftData) => {
draftData.lastCommitBranch = lastCommitBranch;
}),
});
},
},
};
......@@ -6,6 +6,7 @@ import { resetServiceWorkersPublicPath } from '../lib/utils/webpack';
import { CODE_SNIPPET_SOURCE_SETTINGS } from './components/code_snippet_alert/constants';
import getCommitSha from './graphql/queries/client/commit_sha.graphql';
import getCurrentBranch from './graphql/queries/client/current_branch.graphql';
import getLastCommitBranchQuery from './graphql/queries/client/last_commit_branch.query.graphql';
import getPipelineEtag from './graphql/queries/client/pipeline_etag.graphql';
import { resolvers } from './graphql/resolvers';
import typeDefs from './graphql/typedefs.graphql';
......@@ -82,6 +83,13 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
},
});
cache.writeQuery({
query: getLastCommitBranchQuery,
data: {
lastCommitBranch: '',
},
});
return new Vue({
el,
apolloProvider,
......
......@@ -252,7 +252,8 @@ export default {
.getClient()
.writeQuery({ query: getIsNewCiConfigFile, data: { isNewCiConfigFile: false } });
}
// Keep track of the latest commited content to know
// Keep track of the latest committed content to know
// if the user has made changes to the file that are unsaved.
this.lastCommittedContent = this.currentCiFileContent;
},
......
......@@ -118,7 +118,8 @@ describe('Pipeline Editor | Commit section', () => {
});
it('calls the mutation with the CREATE action', () => {
expect(mockMutate).toHaveBeenCalledTimes(1);
// the extra calls are for updating client queries (currentBranch and lastCommitBranch)
expect(mockMutate).toHaveBeenCalledTimes(3);
expect(mockMutate).toHaveBeenCalledWith({
mutation: commitCreate,
update: expect.any(Function),
......@@ -138,7 +139,7 @@ describe('Pipeline Editor | Commit section', () => {
});
it('calls the mutation with the UPDATE action', () => {
expect(mockMutate).toHaveBeenCalledTimes(1);
expect(mockMutate).toHaveBeenCalledTimes(3);
expect(mockMutate).toHaveBeenCalledWith({
mutation: commitCreate,
update: expect.any(Function),
......@@ -158,7 +159,7 @@ describe('Pipeline Editor | Commit section', () => {
});
it('calls the mutation with the current branch', () => {
expect(mockMutate).toHaveBeenCalledTimes(1);
expect(mockMutate).toHaveBeenCalledTimes(3);
expect(mockMutate).toHaveBeenCalledWith({
mutation: commitCreate,
update: expect.any(Function),
......@@ -181,7 +182,7 @@ describe('Pipeline Editor | Commit section', () => {
it('a second commit submits the latest sha, keeping the form updated', async () => {
await submitCommit();
expect(mockMutate).toHaveBeenCalledTimes(2);
expect(mockMutate).toHaveBeenCalledTimes(6);
expect(mockMutate).toHaveBeenCalledWith({
mutation: commitCreate,
update: expect.any(Function),
......
......@@ -11,7 +11,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import BranchSwitcher from '~/pipeline_editor/components/file_nav/branch_switcher.vue';
import { DEFAULT_FAILURE } from '~/pipeline_editor/constants';
import getAvailableBranches from '~/pipeline_editor/graphql/queries/available_branches.graphql';
import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.graphql';
import {
mockBranchPaginationLimit,
mockDefaultBranch,
......@@ -22,6 +22,7 @@ import {
mockTotalBranches,
mockTotalBranchResults,
mockTotalSearchResults,
mockNewBranch,
} from '../../mock_data';
const localVue = createLocalVue();
......@@ -31,9 +32,12 @@ describe('Pipeline editor branch switcher', () => {
let wrapper;
let mockApollo;
let mockAvailableBranchQuery;
let mockCurrentBranchQuery;
let mockLastCommitBranchQuery;
const createComponent = (
{ isQueryLoading, mountFn, options } = {
{ currentBranch, isQueryLoading, mountFn, options } = {
currentBranch: mockDefaultBranch,
isQueryLoading: false,
mountFn: shallowMount,
options: {},
......@@ -59,7 +63,7 @@ describe('Pipeline editor branch switcher', () => {
data() {
return {
availableBranches: ['main'],
currentBranch: mockDefaultBranch,
currentBranch,
};
},
...options,
......@@ -67,8 +71,18 @@ describe('Pipeline editor branch switcher', () => {
};
const createComponentWithApollo = (mountFn = shallowMount) => {
const handlers = [[getAvailableBranches, mockAvailableBranchQuery]];
mockApollo = createMockApollo(handlers);
const handlers = [[getAvailableBranchesQuery, mockAvailableBranchQuery]];
const resolvers = {
Query: {
currentBranch() {
return mockCurrentBranchQuery();
},
lastCommitBranch() {
return mockLastCommitBranchQuery();
},
},
};
mockApollo = createMockApollo(handlers, resolvers);
createComponent({
mountFn,
......@@ -76,11 +90,6 @@ describe('Pipeline editor branch switcher', () => {
localVue,
apolloProvider: mockApollo,
mocks: {},
data() {
return {
currentBranch: mockDefaultBranch,
};
},
},
});
};
......@@ -90,9 +99,24 @@ describe('Pipeline editor branch switcher', () => {
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
const findInfiniteScroll = () => wrapper.findComponent(GlInfiniteScroll);
const defaultBranchInDropdown = () => findDropdownItems().at(0);
const setMockResolvedValues = ({ availableBranches, currentBranch, lastCommitBranch }) => {
if (availableBranches) {
mockAvailableBranchQuery.mockResolvedValue(availableBranches);
}
if (currentBranch) {
mockCurrentBranchQuery.mockResolvedValue(currentBranch);
}
mockLastCommitBranchQuery.mockResolvedValue(lastCommitBranch || '');
};
beforeEach(() => {
mockAvailableBranchQuery = jest.fn();
mockCurrentBranchQuery = jest.fn();
mockLastCommitBranchQuery = jest.fn();
});
afterEach(() => {
......@@ -121,7 +145,10 @@ describe('Pipeline editor branch switcher', () => {
describe('after querying', () => {
beforeEach(async () => {
mockAvailableBranchQuery.mockResolvedValue(mockProjectBranches);
setMockResolvedValues({
availableBranches: mockProjectBranches,
currentBranch: mockDefaultBranch,
});
createComponentWithApollo(mount);
await waitForPromises();
});
......@@ -136,10 +163,8 @@ describe('Pipeline editor branch switcher', () => {
});
it('renders current branch with a check mark', () => {
const defaultBranchInDropdown = findDropdownItems().at(0);
expect(defaultBranchInDropdown.text()).toBe(mockDefaultBranch);
expect(defaultBranchInDropdown.props('isChecked')).toBe(true);
expect(defaultBranchInDropdown().text()).toBe(mockDefaultBranch);
expect(defaultBranchInDropdown().props('isChecked')).toBe(true);
});
it('does not render check mark for other branches', () => {
......@@ -152,7 +177,10 @@ describe('Pipeline editor branch switcher', () => {
describe('on fetch error', () => {
beforeEach(async () => {
mockAvailableBranchQuery.mockResolvedValue(new Error());
setMockResolvedValues({
availableBranches: new Error(),
currentBranch: mockDefaultBranch,
});
createComponentWithApollo();
await waitForPromises();
});
......@@ -169,7 +197,10 @@ describe('Pipeline editor branch switcher', () => {
describe('when switching branches', () => {
beforeEach(async () => {
jest.spyOn(window.history, 'pushState').mockImplementation(() => {});
mockAvailableBranchQuery.mockResolvedValue(mockProjectBranches);
setMockResolvedValues({
availableBranches: mockProjectBranches,
currentBranch: mockDefaultBranch,
});
createComponentWithApollo(mount);
await waitForPromises();
});
......@@ -216,7 +247,10 @@ describe('Pipeline editor branch switcher', () => {
describe('when searching', () => {
beforeEach(async () => {
mockAvailableBranchQuery.mockResolvedValue(mockProjectBranches);
setMockResolvedValues({
availableBranches: mockProjectBranches,
currentBranch: mockDefaultBranch,
});
createComponentWithApollo(mount);
await waitForPromises();
});
......@@ -316,7 +350,10 @@ describe('Pipeline editor branch switcher', () => {
describe('when scrolling to the bottom of the list', () => {
beforeEach(async () => {
mockAvailableBranchQuery.mockResolvedValue(mockProjectBranches);
setMockResolvedValues({
availableBranches: mockProjectBranches,
currentBranch: mockDefaultBranch,
});
createComponentWithApollo();
await waitForPromises();
});
......@@ -372,4 +409,35 @@ describe('Pipeline editor branch switcher', () => {
});
});
});
describe('when committing a new branch', () => {
const createNewBranch = async () => {
setMockResolvedValues({
currentBranch: mockNewBranch,
lastCommitBranch: mockNewBranch,
});
await wrapper.vm.$apollo.queries.currentBranch.refetch();
await wrapper.vm.$apollo.queries.lastCommitBranch.refetch();
};
beforeEach(async () => {
setMockResolvedValues({
availableBranches: mockProjectBranches,
currentBranch: mockDefaultBranch,
});
createComponentWithApollo(mount);
await waitForPromises();
await createNewBranch();
});
it('sets new branch as current branch', () => {
expect(defaultBranchInDropdown().text()).toBe(mockNewBranch);
expect(defaultBranchInDropdown().props('isChecked')).toBe(true);
});
it('adds new branch to branch switcher', () => {
expect(defaultBranchInDropdown().text()).toBe(mockNewBranch);
expect(findDropdownItems()).toHaveLength(mockTotalBranchResults + 1);
});
});
});
......@@ -5,6 +5,7 @@ export const mockProjectNamespace = 'user1';
export const mockProjectPath = 'project1';
export const mockProjectFullPath = `${mockProjectNamespace}/${mockProjectPath}`;
export const mockDefaultBranch = 'main';
export const mockNewBranch = 'new-branch';
export const mockNewMergeRequestPath = '/-/merge_requests/new';
export const mockCommitSha = 'aabbccdd';
export const mockCommitNextSha = 'eeffgghh';
......
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