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>
import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
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 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';
import CommitForm from './commit_form.vue';
......@@ -32,10 +38,14 @@ export default {
data() {
return {
commit: {},
isNewCiConfigFile: false,
isSaving: false,
};
},
apollo: {
isNewCiConfigFile: {
query: getIsNewCiConfigFile,
},
commitSha: {
query: getCommitSha,
},
......@@ -44,6 +54,9 @@ export default {
},
},
computed: {
action() {
return this.isNewCiConfigFile ? COMMIT_ACTION_CREATE : COMMIT_ACTION_UPDATE;
},
defaultCommitMessage() {
return sprintf(this.$options.i18n.defaultCommitMessage, { sourcePath: this.ciConfigPath });
},
......@@ -70,6 +83,7 @@ export default {
} = await this.$apollo.mutate({
mutation: commitCIFile,
variables: {
action: this.action,
projectPath: this.projectFullPath,
branch: targetBranch,
startBranch: this.currentBranch,
......
......@@ -13,3 +13,6 @@ export const MERGED_TAB = 'MERGED_TAB';
export const VISUALIZE_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(
$action: CommitActionMode!
$projectPath: ID!
$branch: String!
$startBranch: String
......@@ -14,7 +15,7 @@ mutation commitCIFile(
startBranch: $startBranch
message: $message
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 }
import getBlobContent from './graphql/queries/blob_content.graphql';
import getCiConfigData from './graphql/queries/ci_config.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';
export default {
......@@ -35,7 +36,7 @@ export default {
failureType: null,
failureReasons: [],
showStartScreen: false,
isNewConfigFile: false,
isNewCiConfigFile: false,
initialCiFileContent: '',
lastCommittedContent: '',
currentCiFileContent: '',
......@@ -47,10 +48,12 @@ export default {
apollo: {
initialCiFileContent: {
query: getBlobContent,
// If we are working off a new file, we don't want to fetch
// the base data as there is nothing to fetch.
skip({ isNewConfigFile }) {
return isNewConfigFile;
// If it's a brand new file, we don't want to fetch the content.
// Then when the user commits the first time, the query would run
// to get the initial file content, but we already have it in `lastCommitedContent`
// so we skip the loading altogether.
skip({ isNewCiConfigFile, lastCommittedContent }) {
return isNewCiConfigFile || lastCommittedContent;
},
variables() {
return {
......@@ -98,6 +101,9 @@ export default {
currentBranch: {
query: getCurrentBranch,
},
isNewCiConfigFile: {
query: getIsNewCiConfigFile,
},
},
computed: {
hasUnsavedChanges() {
......@@ -191,8 +197,10 @@ export default {
this.currentCiFileContent = this.lastCommittedContent;
},
setNewEmptyCiConfigFile() {
this.$apollo
.getClient()
.writeQuery({ query: getIsNewCiConfigFile, data: { isNewCiConfigFile: true } });
this.showStartScreen = false;
this.isNewConfigFile = true;
},
showErrorAlert({ type, reasons = [] }) {
this.reportFailure(type, reasons);
......@@ -202,6 +210,12 @@ export default {
},
updateOnCommit({ 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
// if the user has made changes to the file that are unsaved.
this.lastCommittedContent = this.currentCiFileContent;
......
......@@ -3,7 +3,11 @@ import { mount } from '@vue/test-utils';
import { objectToQuery, redirectTo } from '~/lib/utils/url_utility';
import CommitForm from '~/pipeline_editor/components/commit/commit_form.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 {
......@@ -25,6 +29,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
}));
const mockVariables = {
action: COMMIT_ACTION_UPDATE,
projectPath: mockProjectFullPath,
startBranch: mockDefaultBranch,
message: mockCommitMessage,
......@@ -64,6 +69,7 @@ describe('Pipeline Editor | Commit section', () => {
return {
commitSha: mockCommitSha,
currentBranch: mockDefaultBranch,
isNewCiConfigFile: Boolean(options?.isNewCiConfigfile),
};
},
mocks: {
......@@ -100,19 +106,54 @@ describe('Pipeline Editor | Commit section', () => {
await findCancelBtn().trigger('click');
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
mockMutate.mockReset();
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', () => {
beforeEach(async () => {
createComponent();
await submitCommit();
});
......@@ -157,6 +198,7 @@ describe('Pipeline Editor | Commit section', () => {
const newBranch = 'new-branch';
beforeEach(async () => {
createComponent();
await submitCommit({
branch: newBranch,
});
......@@ -178,6 +220,7 @@ describe('Pipeline Editor | Commit section', () => {
const newBranch = 'new-branch';
beforeEach(async () => {
createComponent();
await submitCommit({
branch: newBranch,
openMergeRequest: true,
......@@ -195,6 +238,10 @@ describe('Pipeline Editor | Commit section', () => {
});
describe('when the commit is ocurring', () => {
beforeEach(() => {
createComponent();
});
it('shows a saving state', async () => {
mockMutate.mockImplementationOnce(() => {
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