Commit 74b7763f authored by Sarah Groff Hennigh-Palermo's avatar Sarah Groff Hennigh-Palermo

Merge branch 'pipeline-editor/refactor-commit-sha' into 'master'

Refactor handling of commit sha in pipeline editor

See merge request gitlab-org/gitlab!67542
parents c410054e 60984ff4
...@@ -10,7 +10,6 @@ import { ...@@ -10,7 +10,6 @@ import {
import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql'; import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql';
import updateCurrentBranchMutation from '../../graphql/mutations/update_current_branch.mutation.graphql'; import updateCurrentBranchMutation from '../../graphql/mutations/update_current_branch.mutation.graphql';
import updateLastCommitBranchMutation from '../../graphql/mutations/update_last_commit_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 getCurrentBranch from '../../graphql/queries/client/current_branch.graphql';
import getIsNewCiConfigFile from '../../graphql/queries/client/is_new_ci_config_file.graphql'; import getIsNewCiConfigFile from '../../graphql/queries/client/is_new_ci_config_file.graphql';
import getPipelineEtag from '../../graphql/queries/client/pipeline_etag.graphql'; import getPipelineEtag from '../../graphql/queries/client/pipeline_etag.graphql';
...@@ -37,6 +36,11 @@ export default { ...@@ -37,6 +36,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
commitSha: {
type: String,
required: false,
default: '',
},
}, },
data() { data() {
return { return {
...@@ -49,9 +53,6 @@ export default { ...@@ -49,9 +53,6 @@ export default {
isNewCiConfigFile: { isNewCiConfigFile: {
query: getIsNewCiConfigFile, query: getIsNewCiConfigFile,
}, },
commitSha: {
query: getCommitSha,
},
currentBranch: { currentBranch: {
query: getCurrentBranch, query: getCurrentBranch,
}, },
...@@ -96,13 +97,7 @@ export default { ...@@ -96,13 +97,7 @@ export default {
lastCommitId: this.commitSha, lastCommitId: this.commitSha,
}, },
update(store, { data }) { update(store, { data }) {
const commitSha = data?.commitCreate?.commit?.sha;
const pipelineEtag = data?.commitCreate?.commit?.commitPipelinePath; const pipelineEtag = data?.commitCreate?.commit?.commitPipelinePath;
if (commitSha) {
store.writeQuery({ query: getCommitSha, data: { commitSha } });
}
if (pipelineEtag) { if (pipelineEtag) {
store.writeQuery({ query: getPipelineEtag, data: { pipelineEtag } }); store.writeQuery({ query: getPipelineEtag, data: { pipelineEtag } });
} }
......
...@@ -3,7 +3,6 @@ import { EDITOR_READY_EVENT } from '~/editor/constants'; ...@@ -3,7 +3,6 @@ import { EDITOR_READY_EVENT } from '~/editor/constants';
import { CiSchemaExtension } from '~/editor/extensions/source_editor_ci_schema_ext'; import { CiSchemaExtension } from '~/editor/extensions/source_editor_ci_schema_ext';
import SourceEditor from '~/vue_shared/components/source_editor.vue'; import SourceEditor from '~/vue_shared/components/source_editor.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import getCommitSha from '../../graphql/queries/client/commit_sha.graphql';
export default { export default {
components: { components: {
...@@ -12,14 +11,11 @@ export default { ...@@ -12,14 +11,11 @@ export default {
mixins: [glFeatureFlagMixin()], mixins: [glFeatureFlagMixin()],
inject: ['ciConfigPath', 'projectPath', 'projectNamespace', 'defaultBranch'], inject: ['ciConfigPath', 'projectPath', 'projectNamespace', 'defaultBranch'],
inheritAttrs: false, inheritAttrs: false,
data() { props: {
return {
commitSha: '',
};
},
apollo: {
commitSha: { commitSha: {
query: getCommitSha, type: String,
required: false,
default: '',
}, },
}, },
methods: { methods: {
......
...@@ -158,11 +158,9 @@ export default { ...@@ -158,11 +158,9 @@ export default {
const updatedPath = setUrlParams({ branch_name: newBranch }); const updatedPath = setUrlParams({ branch_name: newBranch });
historyPushState(updatedPath); historyPushState(updatedPath);
this.$emit('updateCommitSha', { newBranch });
// refetching the content will cause a lot of components to re-render, // refetching the content will cause a lot of components to re-render,
// including the text editor which uses the commit sha to register the CI schema // including the text editor which uses the commit sha to register the CI schema
// so we need to make sure the commit sha is updated first // so we need to make sure the currentBranch (and consequently, the commitSha) are updated first
await this.$nextTick(); await this.$nextTick();
this.$emit('refetchContent'); this.$emit('refetchContent');
}, },
......
...@@ -33,6 +33,11 @@ export default { ...@@ -33,6 +33,11 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
commitSha: {
type: String,
required: false,
default: '',
},
isNewCiConfigFile: { isNewCiConfigFile: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -54,7 +59,11 @@ export default { ...@@ -54,7 +59,11 @@ export default {
</script> </script>
<template> <template>
<div class="gl-mb-5"> <div class="gl-mb-5">
<pipeline-status v-if="showPipelineStatus" :class="$options.pipelineStatusClasses" /> <pipeline-status
v-if="showPipelineStatus"
:commit-sha="commitSha"
:class="$options.pipelineStatusClasses"
/>
<validation-segment :class="validationStyling" :ci-config="ciConfigData" /> <validation-segment :class="validationStyling" :ci-config="ciConfigData" />
</div> </div>
</template> </template>
...@@ -3,7 +3,6 @@ import { GlButton, GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; ...@@ -3,7 +3,6 @@ import { GlButton, GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { truncateSha } from '~/lib/utils/text_utility'; import { truncateSha } from '~/lib/utils/text_utility';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import getCommitSha from '~/pipeline_editor/graphql/queries/client/commit_sha.graphql';
import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql'; import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql';
import getPipelineEtag from '~/pipeline_editor/graphql/queries/client/pipeline_etag.graphql'; import getPipelineEtag from '~/pipeline_editor/graphql/queries/client/pipeline_etag.graphql';
import { import {
...@@ -33,10 +32,14 @@ export default { ...@@ -33,10 +32,14 @@ export default {
GlSprintf, GlSprintf,
}, },
inject: ['projectFullPath'], inject: ['projectFullPath'],
apollo: { props: {
commitSha: { commitSha: {
query: getCommitSha, type: String,
required: false,
default: '',
},
}, },
apollo: {
pipelineEtag: { pipelineEtag: {
query: getPipelineEtag, query: getPipelineEtag,
}, },
...@@ -51,7 +54,7 @@ export default { ...@@ -51,7 +54,7 @@ export default {
sha: this.commitSha, sha: this.commitSha,
}; };
}, },
update: (data) => { update(data) {
const { id, commitPath = '', detailedStatus = {} } = data.project?.pipeline || {}; const { id, commitPath = '', detailedStatus = {} } = data.project?.pipeline || {};
return { return {
...@@ -60,6 +63,11 @@ export default { ...@@ -60,6 +63,11 @@ export default {
detailedStatus, detailedStatus,
}; };
}, },
result(res) {
if (res.data?.project?.pipeline) {
this.hasError = false;
}
},
error() { error() {
this.hasError = true; this.hasError = true;
}, },
...@@ -68,7 +76,6 @@ export default { ...@@ -68,7 +76,6 @@ export default {
}, },
data() { data() {
return { return {
commitSha: '',
hasError: false, hasError: false,
}; };
}, },
...@@ -84,7 +91,11 @@ export default { ...@@ -84,7 +91,11 @@ export default {
// (e.g. pipeline is null during fetch when the pipeline hasn't been // (e.g. pipeline is null during fetch when the pipeline hasn't been
// triggered yet), we can just show the loading state until the pipeline // triggered yet), we can just show the loading state until the pipeline
// details are ready to be fetched // details are ready to be fetched
return this.$apollo.queries.pipeline.loading || (!this.hasPipelineData && !this.hasError); return (
this.$apollo.queries.pipeline.loading ||
this.commitSha.length === 0 ||
(!this.hasPipelineData && !this.hasError)
);
}, },
shortSha() { shortSha() {
return truncateSha(this.commitSha); return truncateSha(this.commitSha);
......
...@@ -69,6 +69,11 @@ export default { ...@@ -69,6 +69,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
commitSha: {
type: String,
required: false,
default: '',
},
}, },
apollo: { apollo: {
appStatus: { appStatus: {
...@@ -110,7 +115,7 @@ export default { ...@@ -110,7 +115,7 @@ export default {
@click="setCurrentTab($options.tabConstants.CREATE_TAB)" @click="setCurrentTab($options.tabConstants.CREATE_TAB)"
> >
<ci-editor-header /> <ci-editor-header />
<text-editor :value="ciFileContent" v-on="$listeners" /> <text-editor :commit-sha="commitSha" :value="ciFileContent" v-on="$listeners" />
</editor-tab> </editor-tab>
<editor-tab <editor-tab
class="gl-mb-3" class="gl-mb-3"
......
mutation updateCommitSha($commitSha: String) {
updateCommitSha(commitSha: $commitSha) @client
}
import produce from 'immer'; import produce from 'immer';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import getCommitShaQuery from './queries/client/commit_sha.graphql';
import getCurrentBranchQuery from './queries/client/current_branch.graphql'; import getCurrentBranchQuery from './queries/client/current_branch.graphql';
import getLastCommitBranchQuery from './queries/client/last_commit_branch.query.graphql'; import getLastCommitBranchQuery from './queries/client/last_commit_branch.query.graphql';
...@@ -32,14 +31,6 @@ export const resolvers = { ...@@ -32,14 +31,6 @@ export const resolvers = {
__typename: 'CiLintContent', __typename: 'CiLintContent',
})); }));
}, },
updateCommitSha: (_, { commitSha }, { cache }) => {
cache.writeQuery({
query: getCommitShaQuery,
data: produce(cache.readQuery({ query: getCommitShaQuery }), (draftData) => {
draftData.commitSha = commitSha;
}),
});
},
updateCurrentBranch: (_, { currentBranch }, { cache }) => { updateCurrentBranch: (_, { currentBranch }, { cache }) => {
cache.writeQuery({ cache.writeQuery({
query: getCurrentBranchQuery, query: getCurrentBranchQuery,
......
...@@ -4,7 +4,6 @@ import VueApollo from 'vue-apollo'; ...@@ -4,7 +4,6 @@ import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { resetServiceWorkersPublicPath } from '../lib/utils/webpack'; import { resetServiceWorkersPublicPath } from '../lib/utils/webpack';
import { CODE_SNIPPET_SOURCE_SETTINGS } from './components/code_snippet_alert/constants'; 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 getCurrentBranch from './graphql/queries/client/current_branch.graphql';
import getLastCommitBranchQuery from './graphql/queries/client/last_commit_branch.query.graphql'; import getLastCommitBranchQuery from './graphql/queries/client/last_commit_branch.query.graphql';
import getPipelineEtag from './graphql/queries/client/pipeline_etag.graphql'; import getPipelineEtag from './graphql/queries/client/pipeline_etag.graphql';
...@@ -26,7 +25,6 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { ...@@ -26,7 +25,6 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
const { const {
// Add to apollo cache as it can be updated by future queries // Add to apollo cache as it can be updated by future queries
commitSha,
initialBranchName, initialBranchName,
pipelineEtag, pipelineEtag,
// Add to provide/inject API for static values // Add to provide/inject API for static values
...@@ -69,13 +67,6 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { ...@@ -69,13 +67,6 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
}, },
}); });
cache.writeQuery({
query: getCommitSha,
data: {
commitSha,
},
});
cache.writeQuery({ cache.writeQuery({
query: getPipelineEtag, query: getPipelineEtag,
data: { data: {
......
...@@ -16,11 +16,9 @@ import { ...@@ -16,11 +16,9 @@ import {
LOAD_FAILURE_UNKNOWN, LOAD_FAILURE_UNKNOWN,
STARTER_TEMPLATE_NAME, STARTER_TEMPLATE_NAME,
} from './constants'; } from './constants';
import updateCommitShaMutation from './graphql/mutations/update_commit_sha.mutation.graphql';
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 getAppStatus from './graphql/queries/client/app_status.graphql'; import getAppStatus from './graphql/queries/client/app_status.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 getIsNewCiConfigFile from './graphql/queries/client/is_new_ci_config_file.graphql';
import getTemplate from './graphql/queries/get_starter_template.query.graphql'; import getTemplate from './graphql/queries/get_starter_template.query.graphql';
...@@ -156,7 +154,32 @@ export default { ...@@ -156,7 +154,32 @@ export default {
query: getAppStatus, query: getAppStatus,
}, },
commitSha: { commitSha: {
query: getCommitSha, query: getLatestCommitShaQuery,
variables() {
return {
projectPath: this.projectFullPath,
ref: this.currentBranch,
};
},
update(data) {
const pipelineNodes = data.project?.pipelines?.nodes ?? [];
// it's possible to query for the commit sha too early after an update
// (e.g. after committing a new branch, we might query for the commit sha
// but the pipeline nodes are still empty).
// in this case, we start polling until we get a commit sha.
if (pipelineNodes.length === 0) {
if (![EDITOR_APP_STATUS_LOADING, EDITOR_APP_STATUS_EMPTY].includes(this.appStatus)) {
this.$apollo.queries.commitSha.startPolling(1000);
return this.commitSha;
}
return '';
}
this.$apollo.queries.commitSha.stopPolling();
return pipelineNodes[0].sha;
},
}, },
currentBranch: { currentBranch: {
query: getCurrentBranch, query: getCurrentBranch,
...@@ -257,38 +280,6 @@ export default { ...@@ -257,38 +280,6 @@ export default {
updateCiConfig(ciFileContent) { updateCiConfig(ciFileContent) {
this.currentCiFileContent = ciFileContent; this.currentCiFileContent = ciFileContent;
}, },
async updateCommitSha({ newBranch }) {
let fetchResults;
try {
fetchResults = await this.$apollo.query({
query: getLatestCommitShaQuery,
variables: {
projectPath: this.projectFullPath,
ref: newBranch,
},
});
} catch {
this.showFetchError();
return;
}
if (fetchResults.errors?.length > 0) {
this.showFetchError();
return;
}
const pipelineNodes = fetchResults?.data?.project?.pipelines?.nodes ?? [];
if (pipelineNodes.length === 0) {
return;
}
const commitSha = pipelineNodes[0].sha;
this.$apollo.mutate({
mutation: updateCommitShaMutation,
variables: { commitSha },
});
},
updateOnCommit({ type }) { updateOnCommit({ type }) {
this.reportSuccess(type); this.reportSuccess(type);
...@@ -336,12 +327,12 @@ export default { ...@@ -336,12 +327,12 @@ export default {
:ci-config-data="ciConfigData" :ci-config-data="ciConfigData"
:ci-file-content="currentCiFileContent" :ci-file-content="currentCiFileContent"
:is-new-ci-config-file="isNewCiConfigFile" :is-new-ci-config-file="isNewCiConfigFile"
:commit-sha="commitSha"
@commit="updateOnCommit" @commit="updateOnCommit"
@resetContent="resetContent" @resetContent="resetContent"
@showError="showErrorAlert" @showError="showErrorAlert"
@refetchContent="refetchContent" @refetchContent="refetchContent"
@updateCiConfig="updateCiConfig" @updateCiConfig="updateCiConfig"
@updateCommitSha="updateCommitSha"
/> />
<confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" /> <confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" />
</div> </div>
......
...@@ -25,6 +25,11 @@ export default { ...@@ -25,6 +25,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
commitSha: {
type: String,
required: false,
default: '',
},
isNewCiConfigFile: { isNewCiConfigFile: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -56,15 +61,22 @@ export default { ...@@ -56,15 +61,22 @@ export default {
<pipeline-editor-file-nav v-on="$listeners" /> <pipeline-editor-file-nav v-on="$listeners" />
<pipeline-editor-header <pipeline-editor-header
:ci-config-data="ciConfigData" :ci-config-data="ciConfigData"
:commit-sha="commitSha"
:is-new-ci-config-file="isNewCiConfigFile" :is-new-ci-config-file="isNewCiConfigFile"
/> />
<pipeline-editor-tabs <pipeline-editor-tabs
:ci-config-data="ciConfigData" :ci-config-data="ciConfigData"
:ci-file-content="ciFileContent" :ci-file-content="ciFileContent"
:commit-sha="commitSha"
v-on="$listeners" v-on="$listeners"
@set-current-tab="setCurrentTab" @set-current-tab="setCurrentTab"
/> />
<commit-section v-if="showCommitForm" :ci-file-content="ciFileContent" v-on="$listeners" /> <commit-section
v-if="showCommitForm"
:ci-file-content="ciFileContent"
:commit-sha="commitSha"
v-on="$listeners"
/>
<pipeline-editor-drawer v-if="showPipelineDrawer" /> <pipeline-editor-drawer v-if="showPipelineDrawer" />
</div> </div>
</template> </template>
import { GlFormTextarea, GlFormInput, GlLoadingIcon } from '@gitlab/ui'; import { GlFormTextarea, GlFormInput, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
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';
...@@ -48,7 +49,10 @@ describe('Pipeline Editor | Commit section', () => { ...@@ -48,7 +49,10 @@ describe('Pipeline Editor | Commit section', () => {
let wrapper; let wrapper;
let mockMutate; let mockMutate;
const defaultProps = { ciFileContent: mockCiYml }; const defaultProps = {
ciFileContent: mockCiYml,
commitSha: mockCommitSha,
};
const createComponent = ({ props = {}, options = {}, provide = {} } = {}) => { const createComponent = ({ props = {}, options = {}, provide = {} } = {}) => {
mockMutate = jest.fn().mockResolvedValue({ mockMutate = jest.fn().mockResolvedValue({
...@@ -67,7 +71,6 @@ describe('Pipeline Editor | Commit section', () => { ...@@ -67,7 +71,6 @@ describe('Pipeline Editor | Commit section', () => {
provide: { ...mockProvide, ...provide }, provide: { ...mockProvide, ...provide },
data() { data() {
return { return {
commitSha: mockCommitSha,
currentBranch: mockDefaultBranch, currentBranch: mockDefaultBranch,
isNewCiConfigFile: Boolean(options?.isNewCiConfigfile), isNewCiConfigFile: Boolean(options?.isNewCiConfigfile),
}; };
...@@ -97,8 +100,7 @@ describe('Pipeline Editor | Commit section', () => { ...@@ -97,8 +100,7 @@ describe('Pipeline Editor | Commit section', () => {
await findCommitForm().find('[data-testid="new-mr-checkbox"]').setChecked(openMergeRequest); await findCommitForm().find('[data-testid="new-mr-checkbox"]').setChecked(openMergeRequest);
} }
await findCommitForm().find('[type="submit"]').trigger('click'); await findCommitForm().find('[type="submit"]').trigger('click');
// Simulate the write to local cache that occurs after a commit await waitForPromises();
await wrapper.setData({ commitSha: mockCommitNextSha });
}; };
const cancelCommitForm = async () => { const cancelCommitForm = async () => {
...@@ -188,7 +190,6 @@ describe('Pipeline Editor | Commit section', () => { ...@@ -188,7 +190,6 @@ describe('Pipeline Editor | Commit section', () => {
update: expect.any(Function), update: expect.any(Function),
variables: { variables: {
...mockVariables, ...mockVariables,
lastCommitId: mockCommitNextSha,
branch: mockDefaultBranch, branch: mockDefaultBranch,
}, },
}); });
......
...@@ -42,15 +42,12 @@ describe('Pipeline Editor | Text editor component', () => { ...@@ -42,15 +42,12 @@ describe('Pipeline Editor | Text editor component', () => {
defaultBranch: mockDefaultBranch, defaultBranch: mockDefaultBranch,
glFeatures, glFeatures,
}, },
propsData: {
commitSha: mockCommitSha,
},
attrs: { attrs: {
value: mockCiYml, value: mockCiYml,
}, },
// Simulate graphQL client query result
data() {
return {
commitSha: mockCommitSha,
};
},
listeners: { listeners: {
[EDITOR_READY_EVENT]: editorReadyListener, [EDITOR_READY_EVENT]: editorReadyListener,
}, },
......
...@@ -247,15 +247,6 @@ describe('Pipeline editor branch switcher', () => { ...@@ -247,15 +247,6 @@ describe('Pipeline editor branch switcher', () => {
expect(wrapper.emitted('refetchContent')).toBeUndefined(); expect(wrapper.emitted('refetchContent')).toBeUndefined();
}); });
it('emits the updateCommitSha event when selecting a different branch', async () => {
expect(wrapper.emitted('updateCommitSha')).toBeUndefined();
const branch = findDropdownItems().at(1);
branch.vm.$emit('click');
expect(wrapper.emitted('updateCommitSha')).toHaveLength(1);
});
}); });
describe('when searching', () => { describe('when searching', () => {
......
...@@ -27,13 +27,11 @@ describe('Pipeline Status', () => { ...@@ -27,13 +27,11 @@ describe('Pipeline Status', () => {
wrapper = shallowMount(PipelineStatus, { wrapper = shallowMount(PipelineStatus, {
localVue, localVue,
apolloProvider: mockApollo, apolloProvider: mockApollo,
provide: mockProvide, propsData: {
stubs: { GlLink, GlSprintf },
data() {
return {
commitSha: mockCommitSha, commitSha: mockCommitSha,
};
}, },
provide: mockProvide,
stubs: { GlLink, GlSprintf },
}); });
}; };
......
...@@ -156,30 +156,28 @@ export const mergeUnwrappedCiConfig = (mergedConfig) => { ...@@ -156,30 +156,28 @@ export const mergeUnwrappedCiConfig = (mergedConfig) => {
}; };
}; };
export const mockNewCommitShaResults = { export const mockCommitShaResults = {
data: { data: {
project: { project: {
pipelines: { pipelines: {
nodes: [ nodes: [
{ {
id: 'gid://gitlab/Ci::Pipeline/1', id: 'gid://gitlab/Ci::Pipeline/1',
sha: 'd0d56d363d8a3f67a8ab9fc00207d468f30032ca', sha: mockCommitSha,
path: `/${mockProjectFullPath}/-/pipelines/488`, path: `/${mockProjectFullPath}/-/pipelines/488`,
commitPath: `/${mockProjectFullPath}/-/commit/d0d56d363d8a3f67a8ab9fc00207d468f30032ca`, commitPath: `/${mockProjectFullPath}/-/commit/d0d56d363d8a3f67a8ab9fc00207d468f30032ca`,
}, },
{ ],
id: 'gid://gitlab/Ci::Pipeline/2',
sha: 'fcab2ece40b26f428dfa3aa288b12c3c5bdb06aa',
path: `/${mockProjectFullPath}/-/pipelines/487`,
commitPath: `/${mockProjectFullPath}/-/commit/fcab2ece40b26f428dfa3aa288b12c3c5bdb06aa`,
}, },
{
id: 'gid://gitlab/Ci::Pipeline/3',
sha: '6c16b17c7f94a438ae19a96c285bb49e3c632cf4',
path: `/${mockProjectFullPath}/-/pipelines/433`,
commitPath: `/${mockProjectFullPath}/-/commit/6c16b17c7f94a438ae19a96c285bb49e3c632cf4`,
}, },
], },
};
export const mockEmptyCommitShaResults = {
data: {
project: {
pipelines: {
nodes: [],
}, },
}, },
}, },
......
...@@ -26,9 +26,10 @@ import { ...@@ -26,9 +26,10 @@ import {
mockBlobContentQueryResponseNoCiFile, mockBlobContentQueryResponseNoCiFile,
mockCiYml, mockCiYml,
mockCommitSha, mockCommitSha,
mockCommitShaResults,
mockDefaultBranch, mockDefaultBranch,
mockEmptyCommitShaResults,
mockProjectFullPath, mockProjectFullPath,
mockNewCommitShaResults,
} from './mock_data'; } from './mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -54,7 +55,6 @@ describe('Pipeline editor app component', () => { ...@@ -54,7 +55,6 @@ describe('Pipeline editor app component', () => {
let mockBlobContentData; let mockBlobContentData;
let mockCiConfigData; let mockCiConfigData;
let mockGetTemplate; let mockGetTemplate;
let mockUpdateCommitSha;
let mockLatestCommitShaQuery; let mockLatestCommitShaQuery;
let mockPipelineQuery; let mockPipelineQuery;
...@@ -71,6 +71,11 @@ describe('Pipeline editor app component', () => { ...@@ -71,6 +71,11 @@ describe('Pipeline editor app component', () => {
SourceEditor: MockSourceEditor, SourceEditor: MockSourceEditor,
PipelineEditorEmptyState, PipelineEditorEmptyState,
}, },
data() {
return {
commitSha: '',
};
},
mocks: { mocks: {
$apollo: { $apollo: {
queries: { queries: {
...@@ -96,18 +101,7 @@ describe('Pipeline editor app component', () => { ...@@ -96,18 +101,7 @@ describe('Pipeline editor app component', () => {
[getPipelineQuery, mockPipelineQuery], [getPipelineQuery, mockPipelineQuery],
]; ];
const resolvers = { mockApollo = createMockApollo(handlers);
Query: {
commitSha() {
return mockCommitSha;
},
},
Mutation: {
updateCommitSha: mockUpdateCommitSha,
},
};
mockApollo = createMockApollo(handlers, resolvers);
const options = { const options = {
localVue, localVue,
...@@ -137,7 +131,6 @@ describe('Pipeline editor app component', () => { ...@@ -137,7 +131,6 @@ describe('Pipeline editor app component', () => {
mockBlobContentData = jest.fn(); mockBlobContentData = jest.fn();
mockCiConfigData = jest.fn(); mockCiConfigData = jest.fn();
mockGetTemplate = jest.fn(); mockGetTemplate = jest.fn();
mockUpdateCommitSha = jest.fn();
mockLatestCommitShaQuery = jest.fn(); mockLatestCommitShaQuery = jest.fn();
mockPipelineQuery = jest.fn(); mockPipelineQuery = jest.fn();
}); });
...@@ -159,11 +152,16 @@ describe('Pipeline editor app component', () => { ...@@ -159,11 +152,16 @@ describe('Pipeline editor app component', () => {
beforeEach(() => { beforeEach(() => {
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse); mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse); mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse);
mockLatestCommitShaQuery.mockResolvedValue(mockCommitShaResults);
}); });
describe('when file exists', () => { describe('when file exists', () => {
beforeEach(async () => { beforeEach(async () => {
await createComponentWithApollo(); await createComponentWithApollo();
jest
.spyOn(wrapper.vm.$apollo.queries.commitSha, 'startPolling')
.mockImplementation(jest.fn());
}); });
it('shows pipeline editor home component', () => { it('shows pipeline editor home component', () => {
...@@ -181,18 +179,32 @@ describe('Pipeline editor app component', () => { ...@@ -181,18 +179,32 @@ describe('Pipeline editor app component', () => {
sha: mockCommitSha, sha: mockCommitSha,
}); });
}); });
it('does not poll for the commit sha', () => {
expect(wrapper.vm.$apollo.queries.commitSha.startPolling).toHaveBeenCalledTimes(0);
});
}); });
describe('when no CI config file exists', () => { describe('when no CI config file exists', () => {
it('shows an empty state and does not show editor home component', async () => { beforeEach(async () => {
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponseNoCiFile); mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponseNoCiFile);
await createComponentWithApollo(); await createComponentWithApollo();
jest
.spyOn(wrapper.vm.$apollo.queries.commitSha, 'startPolling')
.mockImplementation(jest.fn());
});
it('shows an empty state and does not show editor home component', async () => {
expect(findEmptyState().exists()).toBe(true); expect(findEmptyState().exists()).toBe(true);
expect(findAlert().exists()).toBe(false); expect(findAlert().exists()).toBe(false);
expect(findEditorHome().exists()).toBe(false); expect(findEditorHome().exists()).toBe(false);
}); });
it('does not poll for the commit sha', () => {
expect(wrapper.vm.$apollo.queries.commitSha.startPolling).toHaveBeenCalledTimes(0);
});
describe('because of a fetching error', () => { describe('because of a fetching error', () => {
it('shows a unkown error message', async () => { it('shows a unkown error message', async () => {
const loadUnknownFailureText = 'The CI configuration was not loaded, please try again.'; const loadUnknownFailureText = 'The CI configuration was not loaded, please try again.';
...@@ -230,6 +242,7 @@ describe('Pipeline editor app component', () => { ...@@ -230,6 +242,7 @@ describe('Pipeline editor app component', () => {
describe('when landing on the empty state with feature flag on', () => { describe('when landing on the empty state with feature flag on', () => {
it('user can click on CTA button and see an empty editor', async () => { it('user can click on CTA button and see an empty editor', async () => {
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponseNoCiFile); mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponseNoCiFile);
mockLatestCommitShaQuery.mockResolvedValue(mockEmptyCommitShaResults);
await createComponentWithApollo({ await createComponentWithApollo({
provide: { provide: {
...@@ -254,9 +267,9 @@ describe('Pipeline editor app component', () => { ...@@ -254,9 +267,9 @@ describe('Pipeline editor app component', () => {
const updateSuccessMessage = 'Your changes have been successfully committed.'; const updateSuccessMessage = 'Your changes have been successfully committed.';
describe('and the commit mutation succeeds', () => { describe('and the commit mutation succeeds', () => {
beforeEach(() => { beforeEach(async () => {
window.scrollTo = jest.fn(); window.scrollTo = jest.fn();
createComponent(); await createComponentWithApollo();
findEditorHome().vm.$emit('commit', { type: COMMIT_SUCCESS }); findEditorHome().vm.$emit('commit', { type: COMMIT_SUCCESS });
}); });
...@@ -268,7 +281,32 @@ describe('Pipeline editor app component', () => { ...@@ -268,7 +281,32 @@ describe('Pipeline editor app component', () => {
it('scrolls to the top of the page to bring attention to the confirmation message', () => { it('scrolls to the top of the page to bring attention to the confirmation message', () => {
expect(window.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' }); expect(window.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
}); });
it('polls for commit sha while pipeline data is not yet available', async () => {
jest
.spyOn(wrapper.vm.$apollo.queries.commitSha, 'startPolling')
.mockImplementation(jest.fn());
// simulate updating current branch (which triggers commitSha refetch)
// while pipeline data is not yet available
mockLatestCommitShaQuery.mockResolvedValue(mockEmptyCommitShaResults);
await wrapper.vm.$apollo.queries.commitSha.refetch();
expect(wrapper.vm.$apollo.queries.commitSha.startPolling).toHaveBeenCalledTimes(1);
});
it('stops polling for commit sha when pipeline data is available', async () => {
jest
.spyOn(wrapper.vm.$apollo.queries.commitSha, 'stopPolling')
.mockImplementation(jest.fn());
mockLatestCommitShaQuery.mockResolvedValue(mockCommitShaResults);
await wrapper.vm.$apollo.queries.commitSha.refetch();
expect(wrapper.vm.$apollo.queries.commitSha.stopPolling).toHaveBeenCalledTimes(1);
});
}); });
describe('and the commit mutation fails', () => { describe('and the commit mutation fails', () => {
const commitFailedReasons = ['Commit failed']; const commitFailedReasons = ['Commit failed'];
...@@ -320,6 +358,10 @@ describe('Pipeline editor app component', () => { ...@@ -320,6 +358,10 @@ describe('Pipeline editor app component', () => {
}); });
describe('when refetching content', () => { describe('when refetching content', () => {
beforeEach(() => {
mockLatestCommitShaQuery.mockResolvedValue(mockCommitShaResults);
});
it('refetches blob content', async () => { it('refetches blob content', async () => {
await createComponentWithApollo(); await createComponentWithApollo();
jest jest
...@@ -352,6 +394,7 @@ describe('Pipeline editor app component', () => { ...@@ -352,6 +394,7 @@ describe('Pipeline editor app component', () => {
const originalLocation = window.location.href; const originalLocation = window.location.href;
beforeEach(() => { beforeEach(() => {
mockLatestCommitShaQuery.mockResolvedValue(mockCommitShaResults);
setWindowLocation('?template=Android'); setWindowLocation('?template=Android');
}); });
...@@ -371,45 +414,4 @@ describe('Pipeline editor app component', () => { ...@@ -371,45 +414,4 @@ describe('Pipeline editor app component', () => {
expect(findTextEditor().exists()).toBe(true); expect(findTextEditor().exists()).toBe(true);
}); });
}); });
describe('when updating commit sha', () => {
const newCommitSha = mockNewCommitShaResults.data.project.pipelines.nodes[0].sha;
beforeEach(async () => {
mockUpdateCommitSha.mockResolvedValue(newCommitSha);
mockLatestCommitShaQuery.mockResolvedValue(mockNewCommitShaResults);
await createComponentWithApollo();
});
it('fetches updated commit sha for the new branch', async () => {
expect(mockLatestCommitShaQuery).not.toHaveBeenCalled();
wrapper
.findComponent(PipelineEditorHome)
.vm.$emit('updateCommitSha', { newBranch: 'new-branch' });
await waitForPromises();
expect(mockLatestCommitShaQuery).toHaveBeenCalledWith({
projectPath: mockProjectFullPath,
ref: 'new-branch',
});
});
it('updates commit sha with the newly fetched commit sha', async () => {
expect(mockUpdateCommitSha).not.toHaveBeenCalled();
wrapper
.findComponent(PipelineEditorHome)
.vm.$emit('updateCommitSha', { newBranch: 'new-branch' });
await waitForPromises();
expect(mockUpdateCommitSha).toHaveBeenCalled();
expect(mockUpdateCommitSha).toHaveBeenCalledWith(
expect.any(Object),
{ commitSha: mockNewCommitShaResults.data.project.pipelines.nodes[0].sha },
expect.any(Object),
expect.any(Object),
);
});
});
}); });
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