Commit 1c44fed6 authored by Frédéric Caplette's avatar Frédéric Caplette Committed by Sarah Groff Hennigh-Palermo

Allow user to commit new files in pipeline editor

This changes how graphQL mutations work on the FE to commit
files. We check if we should create a new file or update an existing
one, so users can now create new files if required.
parent 47b30b7c
<script> <script>
import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility'; import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import { __, s__, sprintf } from '~/locale'; import { __, s__, sprintf } from '~/locale';
import { COMMIT_FAILURE, COMMIT_SUCCESS } from '../../constants'; import {
COMMIT_ACTION_CREATE,
COMMIT_ACTION_UPDATE,
COMMIT_FAILURE,
COMMIT_SUCCESS,
} from '../../constants';
import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql'; import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql';
import getCommitSha from '../../graphql/queries/client/commit_sha.graphql'; import getCommitSha from '../../graphql/queries/client/commit_sha.graphql';
import getCurrentBranch from '../../graphql/queries/client/current_branch.graphql'; import getCurrentBranch from '../../graphql/queries/client/current_branch.graphql';
import getIsNewCiConfigFile from '../../graphql/queries/client/is_new_ci_config_file.graphql';
import CommitForm from './commit_form.vue'; import CommitForm from './commit_form.vue';
...@@ -32,10 +38,14 @@ export default { ...@@ -32,10 +38,14 @@ export default {
data() { data() {
return { return {
commit: {}, commit: {},
isNewCiConfigFile: false,
isSaving: false, isSaving: false,
}; };
}, },
apollo: { apollo: {
isNewCiConfigFile: {
query: getIsNewCiConfigFile,
},
commitSha: { commitSha: {
query: getCommitSha, query: getCommitSha,
}, },
...@@ -44,6 +54,9 @@ export default { ...@@ -44,6 +54,9 @@ export default {
}, },
}, },
computed: { computed: {
action() {
return this.isNewCiConfigFile ? COMMIT_ACTION_CREATE : COMMIT_ACTION_UPDATE;
},
defaultCommitMessage() { defaultCommitMessage() {
return sprintf(this.$options.i18n.defaultCommitMessage, { sourcePath: this.ciConfigPath }); return sprintf(this.$options.i18n.defaultCommitMessage, { sourcePath: this.ciConfigPath });
}, },
...@@ -70,6 +83,7 @@ export default { ...@@ -70,6 +83,7 @@ export default {
} = await this.$apollo.mutate({ } = await this.$apollo.mutate({
mutation: commitCIFile, mutation: commitCIFile,
variables: { variables: {
action: this.action,
projectPath: this.projectFullPath, projectPath: this.projectFullPath,
branch: targetBranch, branch: targetBranch,
startBranch: this.currentBranch, startBranch: this.currentBranch,
......
...@@ -13,3 +13,6 @@ export const MERGED_TAB = 'MERGED_TAB'; ...@@ -13,3 +13,6 @@ export const MERGED_TAB = 'MERGED_TAB';
export const VISUALIZE_TAB = 'VISUALIZE_TAB'; export const VISUALIZE_TAB = 'VISUALIZE_TAB';
export const TABS_WITH_COMMIT_FORM = [CREATE_TAB, LINT_TAB, VISUALIZE_TAB]; export const TABS_WITH_COMMIT_FORM = [CREATE_TAB, LINT_TAB, VISUALIZE_TAB];
export const COMMIT_ACTION_CREATE = 'CREATE';
export const COMMIT_ACTION_UPDATE = 'UPDATE';
mutation commitCIFile( mutation commitCIFile(
$action: CommitActionMode!
$projectPath: ID! $projectPath: ID!
$branch: String! $branch: String!
$startBranch: String $startBranch: String
...@@ -14,7 +15,7 @@ mutation commitCIFile( ...@@ -14,7 +15,7 @@ mutation commitCIFile(
startBranch: $startBranch startBranch: $startBranch
message: $message message: $message
actions: [ actions: [
{ action: UPDATE, filePath: $filePath, lastCommitId: $lastCommitId, content: $content } { action: $action, filePath: $filePath, lastCommitId: $lastCommitId, content: $content }
] ]
} }
) { ) {
......
...@@ -10,6 +10,7 @@ import { COMMIT_FAILURE, COMMIT_SUCCESS, DEFAULT_FAILURE, LOAD_FAILURE_UNKNOWN } ...@@ -10,6 +10,7 @@ import { COMMIT_FAILURE, COMMIT_SUCCESS, DEFAULT_FAILURE, LOAD_FAILURE_UNKNOWN }
import getBlobContent from './graphql/queries/blob_content.graphql'; import getBlobContent from './graphql/queries/blob_content.graphql';
import getCiConfigData from './graphql/queries/ci_config.graphql'; import getCiConfigData from './graphql/queries/ci_config.graphql';
import getCurrentBranch from './graphql/queries/client/current_branch.graphql'; import getCurrentBranch from './graphql/queries/client/current_branch.graphql';
import getIsNewCiConfigFile from './graphql/queries/client/is_new_ci_config_file.graphql';
import PipelineEditorHome from './pipeline_editor_home.vue'; import PipelineEditorHome from './pipeline_editor_home.vue';
export default { export default {
...@@ -35,7 +36,7 @@ export default { ...@@ -35,7 +36,7 @@ export default {
failureType: null, failureType: null,
failureReasons: [], failureReasons: [],
showStartScreen: false, showStartScreen: false,
isNewConfigFile: false, isNewCiConfigFile: false,
initialCiFileContent: '', initialCiFileContent: '',
lastCommittedContent: '', lastCommittedContent: '',
currentCiFileContent: '', currentCiFileContent: '',
...@@ -47,10 +48,12 @@ export default { ...@@ -47,10 +48,12 @@ export default {
apollo: { apollo: {
initialCiFileContent: { initialCiFileContent: {
query: getBlobContent, query: getBlobContent,
// If we are working off a new file, we don't want to fetch // If it's a brand new file, we don't want to fetch the content.
// the base data as there is nothing to fetch. // Then when the user commits the first time, the query would run
skip({ isNewConfigFile }) { // to get the initial file content, but we already have it in `lastCommitedContent`
return isNewConfigFile; // so we skip the loading altogether.
skip({ isNewCiConfigFile, lastCommittedContent }) {
return isNewCiConfigFile || lastCommittedContent;
}, },
variables() { variables() {
return { return {
...@@ -98,6 +101,9 @@ export default { ...@@ -98,6 +101,9 @@ export default {
currentBranch: { currentBranch: {
query: getCurrentBranch, query: getCurrentBranch,
}, },
isNewCiConfigFile: {
query: getIsNewCiConfigFile,
},
}, },
computed: { computed: {
hasUnsavedChanges() { hasUnsavedChanges() {
...@@ -191,8 +197,10 @@ export default { ...@@ -191,8 +197,10 @@ export default {
this.currentCiFileContent = this.lastCommittedContent; this.currentCiFileContent = this.lastCommittedContent;
}, },
setNewEmptyCiConfigFile() { setNewEmptyCiConfigFile() {
this.$apollo
.getClient()
.writeQuery({ query: getIsNewCiConfigFile, data: { isNewCiConfigFile: true } });
this.showStartScreen = false; this.showStartScreen = false;
this.isNewConfigFile = true;
}, },
showErrorAlert({ type, reasons = [] }) { showErrorAlert({ type, reasons = [] }) {
this.reportFailure(type, reasons); this.reportFailure(type, reasons);
...@@ -202,6 +210,12 @@ export default { ...@@ -202,6 +210,12 @@ export default {
}, },
updateOnCommit({ type }) { updateOnCommit({ type }) {
this.reportSuccess(type); this.reportSuccess(type);
if (this.isNewCiConfigFile) {
this.$apollo
.getClient()
.writeQuery({ query: getIsNewCiConfigFile, data: { isNewCiConfigFile: false } });
}
// Keep track of the latest commited content to know // Keep track of the latest commited content to know
// if the user has made changes to the file that are unsaved. // if the user has made changes to the file that are unsaved.
this.lastCommittedContent = this.currentCiFileContent; this.lastCommittedContent = this.currentCiFileContent;
......
...@@ -3,7 +3,11 @@ import { mount } from '@vue/test-utils'; ...@@ -3,7 +3,11 @@ import { mount } from '@vue/test-utils';
import { objectToQuery, redirectTo } from '~/lib/utils/url_utility'; import { objectToQuery, redirectTo } from '~/lib/utils/url_utility';
import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue'; import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue'; import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue';
import { COMMIT_SUCCESS } from '~/pipeline_editor/constants'; import {
COMMIT_ACTION_CREATE,
COMMIT_ACTION_UPDATE,
COMMIT_SUCCESS,
} from '~/pipeline_editor/constants';
import commitCreate from '~/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql'; import commitCreate from '~/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql';
import { import {
...@@ -25,6 +29,7 @@ jest.mock('~/lib/utils/url_utility', () => ({ ...@@ -25,6 +29,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
})); }));
const mockVariables = { const mockVariables = {
action: COMMIT_ACTION_UPDATE,
projectPath: mockProjectFullPath, projectPath: mockProjectFullPath,
startBranch: mockDefaultBranch, startBranch: mockDefaultBranch,
message: mockCommitMessage, message: mockCommitMessage,
...@@ -64,6 +69,7 @@ describe('Pipeline Editor | Commit section', () => { ...@@ -64,6 +69,7 @@ describe('Pipeline Editor | Commit section', () => {
return { return {
commitSha: mockCommitSha, commitSha: mockCommitSha,
currentBranch: mockDefaultBranch, currentBranch: mockDefaultBranch,
isNewCiConfigFile: Boolean(options?.isNewCiConfigfile),
}; };
}, },
mocks: { mocks: {
...@@ -100,19 +106,54 @@ describe('Pipeline Editor | Commit section', () => { ...@@ -100,19 +106,54 @@ describe('Pipeline Editor | Commit section', () => {
await findCancelBtn().trigger('click'); await findCancelBtn().trigger('click');
}; };
beforeEach(() => {
createComponent();
});
afterEach(() => { afterEach(() => {
mockMutate.mockReset(); mockMutate.mockReset();
wrapper.destroy(); wrapper.destroy();
wrapper = null; });
describe('when the user commits a new file', () => {
beforeEach(async () => {
createComponent({ options: { isNewCiConfigfile: true } });
await submitCommit();
});
it('calls the mutation with the CREATE action', () => {
expect(mockMutate).toHaveBeenCalledTimes(1);
expect(mockMutate).toHaveBeenCalledWith({
mutation: commitCreate,
update: expect.any(Function),
variables: {
...mockVariables,
action: COMMIT_ACTION_CREATE,
branch: mockDefaultBranch,
},
});
});
});
describe('when the user commits an update to an existing file', () => {
beforeEach(async () => {
createComponent();
await submitCommit();
});
it('calls the mutation with the UPDATE action', () => {
expect(mockMutate).toHaveBeenCalledTimes(1);
expect(mockMutate).toHaveBeenCalledWith({
mutation: commitCreate,
update: expect.any(Function),
variables: {
...mockVariables,
action: COMMIT_ACTION_UPDATE,
branch: mockDefaultBranch,
},
});
});
}); });
describe('when the user commits changes to the current branch', () => { describe('when the user commits changes to the current branch', () => {
beforeEach(async () => { beforeEach(async () => {
createComponent();
await submitCommit(); await submitCommit();
}); });
...@@ -157,6 +198,7 @@ describe('Pipeline Editor | Commit section', () => { ...@@ -157,6 +198,7 @@ describe('Pipeline Editor | Commit section', () => {
const newBranch = 'new-branch'; const newBranch = 'new-branch';
beforeEach(async () => { beforeEach(async () => {
createComponent();
await submitCommit({ await submitCommit({
branch: newBranch, branch: newBranch,
}); });
...@@ -178,6 +220,7 @@ describe('Pipeline Editor | Commit section', () => { ...@@ -178,6 +220,7 @@ describe('Pipeline Editor | Commit section', () => {
const newBranch = 'new-branch'; const newBranch = 'new-branch';
beforeEach(async () => { beforeEach(async () => {
createComponent();
await submitCommit({ await submitCommit({
branch: newBranch, branch: newBranch,
openMergeRequest: true, openMergeRequest: true,
...@@ -195,6 +238,10 @@ describe('Pipeline Editor | Commit section', () => { ...@@ -195,6 +238,10 @@ describe('Pipeline Editor | Commit section', () => {
}); });
describe('when the commit is ocurring', () => { describe('when the commit is ocurring', () => {
beforeEach(() => {
createComponent();
});
it('shows a saving state', async () => { it('shows a saving state', async () => {
mockMutate.mockImplementationOnce(() => { mockMutate.mockImplementationOnce(() => {
expect(findCommitBtnLoadingIcon().exists()).toBe(true); expect(findCommitBtnLoadingIcon().exists()).toBe(true);
......
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