Commit 1d2c38db authored by Sarah Groff Hennigh-Palermo's avatar Sarah Groff Hennigh-Palermo

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

Update CommitSha in pipeline editor when switching branches

See merge request gitlab-org/gitlab!64576
parents 1dc8a831 9ee266eb
...@@ -158,6 +158,12 @@ export default { ...@@ -158,6 +158,12 @@ 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,
// 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
await this.$nextTick();
this.$emit('refetchContent'); this.$emit('refetchContent');
}, },
async setSearchTerm(newSearchTerm) { async setSearchTerm(newSearchTerm) {
......
...@@ -66,6 +66,7 @@ export default { ...@@ -66,6 +66,7 @@ export default {
}, },
data() { data() {
return { return {
commitSha: '',
hasError: false, hasError: false,
}; };
}, },
......
mutation updateCommitSha($commitSha: String) {
updateCommitSha(commitSha: $commitSha) @client
}
query getLatestCommitSha($projectPath: ID!, $ref: String) {
project(fullPath: $projectPath) {
pipelines(ref: $ref) {
nodes {
id
sha
path
commitPath
}
}
}
}
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';
...@@ -31,7 +32,15 @@ export const resolvers = { ...@@ -31,7 +32,15 @@ export const resolvers = {
__typename: 'CiLintContent', __typename: 'CiLintContent',
})); }));
}, },
updateCurrentBranch: (_, { currentBranch = undefined }, { cache }) => { updateCommitSha: (_, { commitSha }, { cache }) => {
cache.writeQuery({
query: getCommitShaQuery,
data: produce(cache.readQuery({ query: getCommitShaQuery }), (draftData) => {
draftData.commitSha = commitSha;
}),
});
},
updateCurrentBranch: (_, { currentBranch }, { cache }) => {
cache.writeQuery({ cache.writeQuery({
query: getCurrentBranchQuery, query: getCurrentBranchQuery,
data: produce(cache.readQuery({ query: getCurrentBranchQuery }), (draftData) => { data: produce(cache.readQuery({ query: getCurrentBranchQuery }), (draftData) => {
...@@ -39,7 +48,7 @@ export const resolvers = { ...@@ -39,7 +48,7 @@ export const resolvers = {
}), }),
}); });
}, },
updateLastCommitBranch: (_, { lastCommitBranch = undefined }, { cache }) => { updateLastCommitBranch: (_, { lastCommitBranch }, { cache }) => {
cache.writeQuery({ cache.writeQuery({
query: getLastCommitBranchQuery, query: getLastCommitBranchQuery,
data: produce(cache.readQuery({ query: getLastCommitBranchQuery }), (draftData) => { data: produce(cache.readQuery({ query: getLastCommitBranchQuery }), (draftData) => {
......
...@@ -16,12 +16,14 @@ import { ...@@ -16,12 +16,14 @@ 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 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';
import getLatestCommitShaQuery from './graphql/queries/latest_commit_sha.query.graphql';
import PipelineEditorHome from './pipeline_editor_home.vue'; import PipelineEditorHome from './pipeline_editor_home.vue';
export default { export default {
...@@ -250,6 +252,38 @@ export default { ...@@ -250,6 +252,38 @@ 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);
...@@ -302,6 +336,7 @@ export default { ...@@ -302,6 +336,7 @@ export default {
@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>
......
...@@ -9,7 +9,9 @@ module Ci ...@@ -9,7 +9,9 @@ module Ci
end end
def js_pipeline_editor_data(project) def js_pipeline_editor_data(project)
commit_sha = project.commit ? project.commit.sha : '' initial_branch = params[:branch_name]
latest_commit = project.repository.commit(initial_branch) || project.commit
commit_sha = latest_commit ? latest_commit.sha : ''
{ {
"ci-config-path": project.ci_config_path_or_default, "ci-config-path": project.ci_config_path_or_default,
"ci-examples-help-page-path" => help_page_path('ci/examples/index'), "ci-examples-help-page-path" => help_page_path('ci/examples/index'),
...@@ -17,11 +19,11 @@ module Ci ...@@ -17,11 +19,11 @@ module Ci
"commit-sha" => commit_sha, "commit-sha" => commit_sha,
"default-branch" => project.default_branch_or_main, "default-branch" => project.default_branch_or_main,
"empty-state-illustration-path" => image_path('illustrations/empty-state/empty-dag-md.svg'), "empty-state-illustration-path" => image_path('illustrations/empty-state/empty-dag-md.svg'),
"initial-branch-name": params[:branch_name], "initial-branch-name" => initial_branch,
"lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'), "lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'),
"needs-help-page-path" => help_page_path('ci/yaml/README', anchor: 'needs'), "needs-help-page-path" => help_page_path('ci/yaml/README', anchor: 'needs'),
"new-merge-request-path" => namespace_project_new_merge_request_path, "new-merge-request-path" => namespace_project_new_merge_request_path,
"pipeline_etag" => project.commit ? graphql_etag_pipeline_sha_path(commit_sha) : '', "pipeline_etag" => latest_commit ? graphql_etag_pipeline_sha_path(commit_sha) : '',
"pipeline-page-path" => project_pipelines_path(project), "pipeline-page-path" => project_pipelines_path(project),
"project-path" => project.path, "project-path" => project.path,
"project-full-path" => project.full_path, "project-full-path" => project.full_path,
......
...@@ -207,7 +207,8 @@ describe('Pipeline editor branch switcher', () => { ...@@ -207,7 +207,8 @@ describe('Pipeline editor branch switcher', () => {
it('updates session history when selecting a different branch', async () => { it('updates session history when selecting a different branch', async () => {
const branch = findDropdownItems().at(1); const branch = findDropdownItems().at(1);
await branch.vm.$emit('click'); branch.vm.$emit('click');
await waitForPromises();
expect(window.history.pushState).toHaveBeenCalled(); expect(window.history.pushState).toHaveBeenCalled();
expect(window.history.pushState.mock.calls[0][2]).toContain(`?branch_name=${branch.text()}`); expect(window.history.pushState.mock.calls[0][2]).toContain(`?branch_name=${branch.text()}`);
...@@ -215,7 +216,8 @@ describe('Pipeline editor branch switcher', () => { ...@@ -215,7 +216,8 @@ describe('Pipeline editor branch switcher', () => {
it('does not update session history when selecting current branch', async () => { it('does not update session history when selecting current branch', async () => {
const branch = findDropdownItems().at(0); const branch = findDropdownItems().at(0);
await branch.vm.$emit('click'); branch.vm.$emit('click');
await waitForPromises();
expect(branch.text()).toBe(mockDefaultBranch); expect(branch.text()).toBe(mockDefaultBranch);
expect(window.history.pushState).not.toHaveBeenCalled(); expect(window.history.pushState).not.toHaveBeenCalled();
...@@ -227,7 +229,8 @@ describe('Pipeline editor branch switcher', () => { ...@@ -227,7 +229,8 @@ describe('Pipeline editor branch switcher', () => {
expect(branch.text()).not.toBe(mockDefaultBranch); expect(branch.text()).not.toBe(mockDefaultBranch);
expect(wrapper.emitted('refetchContent')).toBeUndefined(); expect(wrapper.emitted('refetchContent')).toBeUndefined();
await branch.vm.$emit('click'); branch.vm.$emit('click');
await waitForPromises();
expect(wrapper.emitted('refetchContent')).toBeDefined(); expect(wrapper.emitted('refetchContent')).toBeDefined();
expect(wrapper.emitted('refetchContent')).toHaveLength(1); expect(wrapper.emitted('refetchContent')).toHaveLength(1);
...@@ -239,10 +242,20 @@ describe('Pipeline editor branch switcher', () => { ...@@ -239,10 +242,20 @@ describe('Pipeline editor branch switcher', () => {
expect(branch.text()).toBe(mockDefaultBranch); expect(branch.text()).toBe(mockDefaultBranch);
expect(wrapper.emitted('refetchContent')).toBeUndefined(); expect(wrapper.emitted('refetchContent')).toBeUndefined();
await branch.vm.$emit('click'); branch.vm.$emit('click');
await waitForPromises();
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', () => {
......
...@@ -156,6 +156,35 @@ export const mergeUnwrappedCiConfig = (mergedConfig) => { ...@@ -156,6 +156,35 @@ export const mergeUnwrappedCiConfig = (mergedConfig) => {
}; };
}; };
export const mockNewCommitShaResults = {
data: {
project: {
pipelines: {
nodes: [
{
id: 'gid://gitlab/Ci::Pipeline/1',
sha: 'd0d56d363d8a3f67a8ab9fc00207d468f30032ca',
path: `/${mockProjectFullPath}/-/pipelines/488`,
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 mockProjectBranches = { export const mockProjectBranches = {
data: { data: {
project: { project: {
......
...@@ -12,7 +12,9 @@ import PipelineEditorMessages from '~/pipeline_editor/components/ui/pipeline_edi ...@@ -12,7 +12,9 @@ import PipelineEditorMessages from '~/pipeline_editor/components/ui/pipeline_edi
import { COMMIT_SUCCESS, COMMIT_FAILURE } from '~/pipeline_editor/constants'; import { COMMIT_SUCCESS, COMMIT_FAILURE } from '~/pipeline_editor/constants';
import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.graphql'; import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.graphql';
import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql'; import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql';
import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql';
import getTemplate from '~/pipeline_editor/graphql/queries/get_starter_template.query.graphql'; import getTemplate from '~/pipeline_editor/graphql/queries/get_starter_template.query.graphql';
import getLatestCommitShaQuery from '~/pipeline_editor/graphql/queries/latest_commit_sha.query.graphql';
import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue'; import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue'; import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue';
import { import {
...@@ -24,6 +26,7 @@ import { ...@@ -24,6 +26,7 @@ import {
mockDefaultBranch, mockDefaultBranch,
mockProjectFullPath, mockProjectFullPath,
mockCiYml, mockCiYml,
mockNewCommitShaResults,
} from './mock_data'; } from './mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -49,6 +52,9 @@ describe('Pipeline editor app component', () => { ...@@ -49,6 +52,9 @@ describe('Pipeline editor app component', () => {
let mockBlobContentData; let mockBlobContentData;
let mockCiConfigData; let mockCiConfigData;
let mockGetTemplate; let mockGetTemplate;
let mockUpdateCommitSha;
let mockLatestCommitShaQuery;
let mockPipelineQuery;
const createComponent = ({ blobLoading = false, options = {}, provide = {} } = {}) => { const createComponent = ({ blobLoading = false, options = {}, provide = {} } = {}) => {
wrapper = shallowMount(PipelineEditorApp, { wrapper = shallowMount(PipelineEditorApp, {
...@@ -84,9 +90,16 @@ describe('Pipeline editor app component', () => { ...@@ -84,9 +90,16 @@ describe('Pipeline editor app component', () => {
[getBlobContent, mockBlobContentData], [getBlobContent, mockBlobContentData],
[getCiConfigData, mockCiConfigData], [getCiConfigData, mockCiConfigData],
[getTemplate, mockGetTemplate], [getTemplate, mockGetTemplate],
[getLatestCommitShaQuery, mockLatestCommitShaQuery],
[getPipelineQuery, mockPipelineQuery],
]; ];
mockApollo = createMockApollo(handlers); const resolvers = {
Mutation: {
updateCommitSha: mockUpdateCommitSha,
},
};
mockApollo = createMockApollo(handlers, resolvers);
const options = { const options = {
localVue, localVue,
...@@ -116,6 +129,9 @@ describe('Pipeline editor app component', () => { ...@@ -116,6 +129,9 @@ 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();
mockPipelineQuery = jest.fn();
}); });
afterEach(() => { afterEach(() => {
...@@ -347,4 +363,45 @@ describe('Pipeline editor app component', () => { ...@@ -347,4 +363,45 @@ 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),
);
});
});
}); });
...@@ -45,7 +45,7 @@ RSpec.describe Ci::PipelineEditorHelper do ...@@ -45,7 +45,7 @@ RSpec.describe Ci::PipelineEditorHelper do
"commit-sha" => project.commit.sha, "commit-sha" => project.commit.sha,
"default-branch" => project.default_branch_or_main, "default-branch" => project.default_branch_or_main,
"empty-state-illustration-path" => 'foo', "empty-state-illustration-path" => 'foo',
"initial-branch-name": nil, "initial-branch-name" => nil,
"lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'), "lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'),
"needs-help-page-path" => help_page_path('ci/yaml/README', anchor: 'needs'), "needs-help-page-path" => help_page_path('ci/yaml/README', anchor: 'needs'),
"new-merge-request-path" => '/mock/project/-/merge_requests/new', "new-merge-request-path" => '/mock/project/-/merge_requests/new',
...@@ -72,7 +72,7 @@ RSpec.describe Ci::PipelineEditorHelper do ...@@ -72,7 +72,7 @@ RSpec.describe Ci::PipelineEditorHelper do
"commit-sha" => '', "commit-sha" => '',
"default-branch" => project.default_branch_or_main, "default-branch" => project.default_branch_or_main,
"empty-state-illustration-path" => 'foo', "empty-state-illustration-path" => 'foo',
"initial-branch-name": nil, "initial-branch-name" => nil,
"lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'), "lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'),
"needs-help-page-path" => help_page_path('ci/yaml/README', anchor: 'needs'), "needs-help-page-path" => help_page_path('ci/yaml/README', anchor: 'needs'),
"new-merge-request-path" => '/mock/project/-/merge_requests/new', "new-merge-request-path" => '/mock/project/-/merge_requests/new',
...@@ -87,5 +87,21 @@ RSpec.describe Ci::PipelineEditorHelper do ...@@ -87,5 +87,21 @@ RSpec.describe Ci::PipelineEditorHelper do
}) })
end end
end end
context 'with a non-default branch name' do
let(:user) { create(:user) }
before do
create_commit('Message', project, user, 'feature')
controller.params[:branch_name] = 'feature'
end
it 'returns correct values' do
latest_feature_sha = project.repository.commit('feature').sha
expect(pipeline_editor_data['initial-branch-name']).to eq('feature')
expect(pipeline_editor_data['commit-sha']).to eq(latest_feature_sha)
end
end
end end
end end
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