Commit 8d03fe1b authored by Olena Horal-Koretska's avatar Olena Horal-Koretska

Merge branch 'pipeline-editor-branch-switcher-query' into 'master'

Fetch and search available branches in pipeline editor

See merge request gitlab-org/gitlab!59217
parents ab76da78 6fc333ec
<script> <script>
import { GlDropdown, GlDropdownItem, GlDropdownSectionHeader, GlIcon } from '@gitlab/ui'; import {
GlDropdown,
GlDropdownItem,
GlDropdownSectionHeader,
GlInfiniteScroll,
GlLoadingIcon,
GlSearchBoxByType,
} from '@gitlab/ui';
import { historyPushState } from '~/lib/utils/common_utils'; import { historyPushState } from '~/lib/utils/common_utils';
import { setUrlParams } from '~/lib/utils/url_utility'; import { setUrlParams } from '~/lib/utils/url_utility';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { DEFAULT_FAILURE } from '~/pipeline_editor/constants'; import {
BRANCH_PAGINATION_LIMIT,
BRANCH_SEARCH_DEBOUNCE,
DEFAULT_FAILURE,
} from '~/pipeline_editor/constants';
import getAvailableBranches from '~/pipeline_editor/graphql/queries/available_branches.graphql'; import getAvailableBranches from '~/pipeline_editor/graphql/queries/available_branches.graphql';
import getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_branch.graphql'; import getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_branch.graphql';
export default { export default {
i18n: { i18n: {
dropdownHeader: s__('Switch Branch'),
title: s__('Branches'), title: s__('Branches'),
fetchError: s__('Unable to fetch branch list for this project.'), fetchError: s__('Unable to fetch branch list for this project.'),
}, },
inputDebounce: BRANCH_SEARCH_DEBOUNCE,
components: { components: {
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlDropdownSectionHeader, GlDropdownSectionHeader,
GlIcon, GlInfiniteScroll,
GlLoadingIcon,
GlSearchBoxByType,
},
inject: ['projectFullPath', 'totalBranches'],
props: {
paginationLimit: {
type: Number,
required: false,
default: BRANCH_PAGINATION_LIMIT,
},
},
data() {
return {
branches: [],
page: {
limit: this.paginationLimit,
offset: 0,
searchTerm: '',
},
};
}, },
inject: ['projectFullPath'],
apollo: { apollo: {
branches: { availableBranches: {
query: getAvailableBranches, query: getAvailableBranches,
variables() { variables() {
return { return {
limit: this.page.limit,
offset: this.page.offset,
projectFullPath: this.projectFullPath, projectFullPath: this.projectFullPath,
searchPattern: this.searchPattern,
}; };
}, },
update(data) { update(data) {
return data.project?.repository?.branches || []; return data.project?.repository?.branchNames || [];
},
result({ data }) {
const newBranches = data.project?.repository?.branchNames || [];
// check that we're not re-concatenating existing fetch results
if (!this.branches.includes(newBranches[0])) {
this.branches = this.branches.concat(newBranches);
}
}, },
error() { error() {
this.$emit('showError', { this.$emit('showError', {
...@@ -42,11 +85,37 @@ export default { ...@@ -42,11 +85,37 @@ export default {
}, },
}, },
computed: { computed: {
hasBranchList() { isBranchesLoading() {
return this.branches?.length > 0; return this.$apollo.queries.availableBranches.loading;
},
showBranchSwitcher() {
return this.branches.length > 0 || this.page.searchTerm.length > 0;
},
searchPattern() {
if (this.page.searchTerm === '') {
return '*';
}
return `*${this.page.searchTerm}*`;
}, },
}, },
methods: { methods: {
// if there is no searchPattern, paginate by {paginationLimit} branches
fetchNextBranches() {
if (
this.isBranchesLoading ||
this.page.searchTerm.length > 0 ||
this.branches.length === this.totalBranches
) {
return;
}
this.page = {
...this.page,
limit: this.paginationLimit,
offset: this.page.offset + this.paginationLimit,
};
},
async selectBranch(newBranch) { async selectBranch(newBranch) {
if (newBranch === this.currentBranch) { if (newBranch === this.currentBranch) {
return; return;
...@@ -62,24 +131,53 @@ export default { ...@@ -62,24 +131,53 @@ export default {
this.$emit('refetchContent'); this.$emit('refetchContent');
}, },
setSearchTerm(newSearchTerm) {
this.branches = [];
this.page = {
limit: newSearchTerm.trim() === '' ? this.paginationLimit : this.totalBranches,
offset: 0,
searchTerm: newSearchTerm.trim(),
};
},
}, },
}; };
</script> </script>
<template> <template>
<gl-dropdown v-if="hasBranchList" class="gl-ml-2" :text="currentBranch" icon="branch"> <gl-dropdown
v-if="showBranchSwitcher"
class="gl-ml-2"
:header-text="$options.i18n.dropdownHeader"
:text="currentBranch"
icon="branch"
>
<gl-search-box-by-type :debounce="$options.inputDebounce" @input="setSearchTerm" />
<gl-dropdown-section-header> <gl-dropdown-section-header>
{{ this.$options.i18n.title }} {{ $options.i18n.title }}
</gl-dropdown-section-header> </gl-dropdown-section-header>
<gl-dropdown-item
v-for="branch in branches" <gl-infinite-scroll
:key="branch.name" :fetched-items="branches.length"
:is-checked="currentBranch === branch.name" :total-items="totalBranches"
:is-check-item="true" :max-list-height="250"
@click="selectBranch(branch.name)" @bottomReached="fetchNextBranches"
> >
<gl-icon name="check" class="gl-visibility-hidden" /> <template #items>
{{ branch.name }} <gl-dropdown-item
</gl-dropdown-item> v-for="branch in branches"
:key="branch"
:is-checked="currentBranch === branch"
:is-check-item="true"
@click="selectBranch(branch)"
>
{{ branch }}
</gl-dropdown-item>
</template>
<template #default>
<gl-dropdown-item v-if="isBranchesLoading" key="loading">
<gl-loading-icon size="md" />
</gl-dropdown-item>
</template>
</gl-infinite-scroll>
</gl-dropdown> </gl-dropdown>
</template> </template>
...@@ -28,3 +28,6 @@ export const COMMIT_ACTION_CREATE = 'CREATE'; ...@@ -28,3 +28,6 @@ export const COMMIT_ACTION_CREATE = 'CREATE';
export const COMMIT_ACTION_UPDATE = 'UPDATE'; export const COMMIT_ACTION_UPDATE = 'UPDATE';
export const DRAWER_EXPANDED_KEY = 'pipeline_editor_drawer_expanded'; export const DRAWER_EXPANDED_KEY = 'pipeline_editor_drawer_expanded';
export const BRANCH_PAGINATION_LIMIT = 20;
export const BRANCH_SEARCH_DEBOUNCE = '500';
query getAvailableBranches($projectFullPath: ID!) { query getAvailableBranches(
project(fullPath: $projectFullPath) @client { $limit: Int!
$offset: Int!
$projectFullPath: ID!
$searchPattern: String!
) {
project(fullPath: $projectFullPath) {
repository { repository {
branches { branchNames(limit: $limit, offset: $offset, searchPattern: $searchPattern)
name
}
} }
} }
} }
...@@ -11,22 +11,6 @@ export const resolvers = { ...@@ -11,22 +11,6 @@ export const resolvers = {
}), }),
}; };
}, },
/* eslint-disable @gitlab/require-i18n-strings */
project() {
return {
__typename: 'Project',
repository: {
__typename: 'Repository',
branches: [
{ __typename: 'Branch', name: 'main' },
{ __typename: 'Branch', name: 'develop' },
{ __typename: 'Branch', name: 'production' },
{ __typename: 'Branch', name: 'test' },
],
},
};
},
/* eslint-enable @gitlab/require-i18n-strings */
}, },
Mutation: { Mutation: {
lintCI: (_, { endpoint, content, dry_run }) => { lintCI: (_, { endpoint, content, dry_run }) => {
......
...@@ -43,6 +43,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { ...@@ -43,6 +43,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
projectPath, projectPath,
projectNamespace, projectNamespace,
runnerHelpPagePath, runnerHelpPagePath,
totalBranches,
ymlHelpPagePath, ymlHelpPagePath,
} = el?.dataset; } = el?.dataset;
...@@ -100,6 +101,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { ...@@ -100,6 +101,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
projectPath, projectPath,
projectNamespace, projectNamespace,
runnerHelpPagePath, runnerHelpPagePath,
totalBranches: parseInt(totalBranches, 10),
ymlHelpPagePath, ymlHelpPagePath,
}, },
render(h) { render(h) {
......
...@@ -27,6 +27,7 @@ module Ci ...@@ -27,6 +27,7 @@ module Ci
"project-full-path" => project.full_path, "project-full-path" => project.full_path,
"project-namespace" => project.namespace.full_path, "project-namespace" => project.namespace.full_path,
"runner-help-page-path" => help_page_path('ci/runners/README'), "runner-help-page-path" => help_page_path('ci/runners/README'),
"total-branches" => project.repository.branches.length,
"yml-help-page-path" => help_page_path('ci/yaml/README') "yml-help-page-path" => help_page_path('ci/yaml/README')
} }
end end
......
...@@ -31474,6 +31474,9 @@ msgstr "" ...@@ -31474,6 +31474,9 @@ msgstr ""
msgid "Survey Response" msgid "Survey Response"
msgstr "" msgstr ""
msgid "Switch Branch"
msgstr ""
msgid "Switch branch/tag" msgid "Switch branch/tag"
msgstr "" msgstr ""
......
...@@ -9,7 +9,6 @@ import { ...@@ -9,7 +9,6 @@ import {
mockDefaultBranch, mockDefaultBranch,
mockLintResponse, mockLintResponse,
mockProjectFullPath, mockProjectFullPath,
mockProjectBranches,
} from '../mock_data'; } from '../mock_data';
jest.mock('~/api', () => { jest.mock('~/api', () => {
...@@ -47,23 +46,6 @@ describe('~/pipeline_editor/graphql/resolvers', () => { ...@@ -47,23 +46,6 @@ describe('~/pipeline_editor/graphql/resolvers', () => {
await expect(result.rawData).resolves.toBe(mockCiYml); await expect(result.rawData).resolves.toBe(mockCiYml);
}); });
}); });
describe('project', () => {
it('resolves project data with type names', async () => {
const result = await resolvers.Query.project();
// eslint-disable-next-line no-underscore-dangle
expect(result.__typename).toBe('Project');
});
it('resolves project with available list of branches', async () => {
const result = await resolvers.Query.project();
expect(result.repository.branches).toHaveLength(
mockProjectBranches.repository.branches.length,
);
});
});
}); });
describe('Mutation', () => { describe('Mutation', () => {
......
...@@ -139,18 +139,54 @@ export const mergeUnwrappedCiConfig = (mergedConfig) => { ...@@ -139,18 +139,54 @@ export const mergeUnwrappedCiConfig = (mergedConfig) => {
}; };
export const mockProjectBranches = { export const mockProjectBranches = {
__typename: 'Project', data: {
repository: { project: {
__typename: 'Repository', repository: {
branches: [ branchNames: [
{ __typename: 'Branch', name: 'main' }, 'main',
{ __typename: 'Branch', name: 'develop' }, 'develop',
{ __typename: 'Branch', name: 'production' }, 'production',
{ __typename: 'Branch', name: 'test' }, 'test',
], 'better-feature',
'feature-abc',
'update-ci',
'mock-feature',
'test-merge-request',
'staging',
],
},
},
}, },
}; };
export const mockTotalBranchResults =
mockProjectBranches.data.project.repository.branchNames.length;
export const mockSearchBranches = {
data: {
project: {
repository: {
branchNames: ['test', 'better-feature', 'update-ci', 'test-merge-request'],
},
},
},
};
export const mockTotalSearchResults = mockSearchBranches.data.project.repository.branchNames.length;
export const mockEmptySearchBranches = {
data: {
project: {
repository: {
branchNames: [],
},
},
},
};
export const mockBranchPaginationLimit = 10;
export const mockTotalBranches = 20; // must be greater than mockBranchPaginationLimit to test pagination
export const mockProjectPipeline = { export const mockProjectPipeline = {
pipeline: { pipeline: {
commitPath: '/-/commit/aabbccdd', commitPath: '/-/commit/aabbccdd',
......
...@@ -55,6 +55,7 @@ RSpec.describe Ci::PipelineEditorHelper do ...@@ -55,6 +55,7 @@ RSpec.describe Ci::PipelineEditorHelper do
"project-full-path" => project.full_path, "project-full-path" => project.full_path,
"project-namespace" => project.namespace.full_path, "project-namespace" => project.namespace.full_path,
"runner-help-page-path" => help_page_path('ci/runners/README'), "runner-help-page-path" => help_page_path('ci/runners/README'),
"total-branches" => project.repository.branches.length,
"yml-help-page-path" => help_page_path('ci/yaml/README') "yml-help-page-path" => help_page_path('ci/yaml/README')
}) })
end end
...@@ -81,6 +82,7 @@ RSpec.describe Ci::PipelineEditorHelper do ...@@ -81,6 +82,7 @@ RSpec.describe Ci::PipelineEditorHelper do
"project-full-path" => project.full_path, "project-full-path" => project.full_path,
"project-namespace" => project.namespace.full_path, "project-namespace" => project.namespace.full_path,
"runner-help-page-path" => help_page_path('ci/runners/README'), "runner-help-page-path" => help_page_path('ci/runners/README'),
"total-branches" => 0,
"yml-help-page-path" => help_page_path('ci/yaml/README') "yml-help-page-path" => help_page_path('ci/yaml/README')
}) })
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