diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue index 5a94d1440314ec267fae06af652278ff83e82537..db9dc74863da469d6ab97ed585f0b126412327a2 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue @@ -98,6 +98,11 @@ export default { type: String, required: true, }, + defaultBranchName: { + type: String, + required: false, + default: null, + }, params: { type: Object, required: true, @@ -347,6 +352,7 @@ export default { <pipelines-filtered-search class="gl-display-flex gl-flex-grow-1 gl-mr-4" :project-id="projectId" + :default-branch-name="defaultBranchName" :params="validatedParams" @filterPipelines="filterPipelines" /> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue index 2dfdaa0ea28f728ddd05e3770f7e9b3117ea5f56..4d28545a0352b14095d9c06f8667c559908b099b 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue @@ -24,6 +24,11 @@ export default { type: String, required: true, }, + defaultBranchName: { + type: String, + required: false, + default: null, + }, params: { type: Object, required: true, @@ -57,6 +62,7 @@ export default { token: PipelineBranchNameToken, operators: OPERATOR_IS_ONLY, projectId: this.projectId, + defaultBranchName: this.defaultBranchName, disabled: this.selectedTypes.includes(this.$options.tagType), }, { diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue index 5409e68cdc46c24e43397da9881a4465a58ab7d5..1db2898b72ac53409d8069e09886e61540342e39 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue @@ -35,6 +35,13 @@ export default { Api.branches(this.config.projectId, searchterm) .then(({ data }) => { this.branches = data.map((branch) => branch.name); + if (!searchterm && this.config.defaultBranchName) { + // Shift the default branch to the top of the list + this.branches = this.branches.filter( + (branch) => branch !== this.config.defaultBranchName, + ); + this.branches.unshift(this.config.defaultBranchName); + } this.loading = false; }) .catch((err) => { diff --git a/app/assets/javascripts/pipelines/pipelines_index.js b/app/assets/javascripts/pipelines/pipelines_index.js index 2b1f5419953bf9fc2ce0d47de83b33d56e33f6ba..f4d9a44a7544f6121af35a91e7f1b176c6440e08 100644 --- a/app/assets/javascripts/pipelines/pipelines_index.js +++ b/app/assets/javascripts/pipelines/pipelines_index.js @@ -36,6 +36,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => { ciLintPath, resetCachePath, projectId, + defaultBranchName, params, ciRunnerSettingsPath, anyRunnersAvailable, @@ -75,6 +76,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => { ciLintPath, resetCachePath, projectId, + defaultBranchName, params: JSON.parse(params), ciRunnerSettingsPath, anyRunnersAvailable: parseBoolean(anyRunnersAvailable), diff --git a/app/helpers/ci/pipelines_helper.rb b/app/helpers/ci/pipelines_helper.rb index d742842b8b62ca818dafa2b2254827291f4c074d..8d2f83409be034c6dc4690224a8dced4d6c13b22 100644 --- a/app/helpers/ci/pipelines_helper.rb +++ b/app/helpers/ci/pipelines_helper.rb @@ -84,6 +84,7 @@ def pipelines_list_data(project, list_url) data = { endpoint: list_url, project_id: project.id, + default_branch_name: project.default_branch, params: params.to_json, artifacts_endpoint: downloadable_artifacts_project_pipeline_path(project, artifacts_endpoint_placeholder, format: :json), artifacts_endpoint_placeholder: artifacts_endpoint_placeholder, diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js index 97b59a0951825471daa08f05c4e98723cbacf0ea..0822b293f75ff1c1f6b0c54a684e60377e4babe9 100644 --- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js +++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js @@ -27,6 +27,7 @@ describe('Pipelines filtered search', () => { wrapper = mount(PipelinesFilteredSearch, { propsData: { projectId: '21', + defaultBranchName: 'main', params, }, attachTo: document.body, @@ -69,6 +70,7 @@ describe('Pipelines filtered search', () => { title: 'Branch name', unique: true, projectId: '21', + defaultBranchName: 'main', operators: OPERATOR_IS_ONLY, }); diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js index ca19a6d337ad6136777420b7cd1d4d6cce3d3304..20ed12cd1f549d03c5b742a54a018fc5061ee24c 100644 --- a/spec/frontend/pipelines/pipelines_spec.js +++ b/spec/frontend/pipelines/pipelines_spec.js @@ -27,6 +27,7 @@ jest.mock('~/flash'); const mockProjectPath = 'twitter/flight'; const mockProjectId = '21'; +const mockDefaultBranchName = 'main'; const mockPipelinesEndpoint = `/${mockProjectPath}/pipelines.json`; const mockPipelinesIds = mockPipelinesResponse.pipelines.map(({ id }) => id); const mockPipelineWithStages = mockPipelinesResponse.pipelines.find( @@ -85,6 +86,7 @@ describe('Pipelines', () => { propsData: { store: new Store(), projectId: mockProjectId, + defaultBranchName: mockDefaultBranchName, endpoint: mockPipelinesEndpoint, params: {}, ...props, diff --git a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js index 2e44f40eda4292ca777deca2282c60bcfb90fd11..42ae154fb5ef24eee65a817ef99acbcd1cc1b7e9 100644 --- a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js +++ b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js @@ -1,5 +1,7 @@ import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui'; +import { nextTick } from 'vue'; import { shallowMount } from '@vue/test-utils'; +import waitForPromises from 'helpers/wait_for_promises'; import Api from '~/api'; import PipelineBranchNameToken from '~/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue'; import { branches, mockBranchesAfterMap } from '../mock_data'; @@ -10,6 +12,8 @@ describe('Pipeline Branch Name Token', () => { const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken); const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + const getBranchSuggestions = () => + findAllFilteredSearchSuggestions().wrappers.map((w) => w.text()); const stubs = { GlFilteredSearchToken: { @@ -24,6 +28,7 @@ describe('Pipeline Branch Name Token', () => { title: 'Branch name', unique: true, projectId: '21', + defaultBranchName: null, disabled: false, }, value: { @@ -31,6 +36,19 @@ describe('Pipeline Branch Name Token', () => { }, }; + const optionsWithDefaultBranchName = (options) => { + return { + propsData: { + ...defaultProps, + config: { + ...defaultProps.config, + defaultBranchName: 'main', + }, + }, + ...options, + }; + }; + const createComponent = (options, data) => { wrapper = shallowMount(PipelineBranchNameToken, { propsData: { @@ -94,5 +112,34 @@ describe('Pipeline Branch Name Token', () => { expect(findAllFilteredSearchSuggestions()).toHaveLength(mockBranches.length); }); + + it('shows the default branch first if no branch was searched for', async () => { + const mockBranches = [{ name: 'branch-1' }]; + jest.spyOn(Api, 'branches').mockResolvedValue({ data: mockBranches }); + + createComponent(optionsWithDefaultBranchName({ stubs }), { loading: false }); + await nextTick(); + expect(getBranchSuggestions()).toEqual(['main', 'branch-1']); + }); + + it('does not show the default branch if a search term was provided', async () => { + const mockBranches = [{ name: 'branch-1' }]; + jest.spyOn(Api, 'branches').mockResolvedValue({ data: mockBranches }); + + createComponent(optionsWithDefaultBranchName(), { loading: false }); + + findFilteredSearchToken().vm.$emit('input', { data: 'branch-1' }); + await waitForPromises(); + expect(getBranchSuggestions()).toEqual(['branch-1']); + }); + + it('shows the default branch only once if it appears in the results', async () => { + const mockBranches = [{ name: 'main' }]; + jest.spyOn(Api, 'branches').mockResolvedValue({ data: mockBranches }); + + createComponent(optionsWithDefaultBranchName({ stubs }), { loading: false }); + await nextTick(); + expect(getBranchSuggestions()).toEqual(['main']); + }); }); }); diff --git a/spec/helpers/ci/pipelines_helper_spec.rb b/spec/helpers/ci/pipelines_helper_spec.rb index a7a96efcb3f11307e5838fbf8570681d2b06fc1c..2b76eaa87bc33c72d44c56914e7a1a7c9d26d447 100644 --- a/spec/helpers/ci/pipelines_helper_spec.rb +++ b/spec/helpers/ci/pipelines_helper_spec.rb @@ -106,6 +106,7 @@ it 'has the expected keys' do expect(subject.keys).to match_array([:endpoint, :project_id, + :default_branch_name, :params, :artifacts_endpoint, :artifacts_endpoint_placeholder,