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>
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 { setUrlParams } from '~/lib/utils/url_utility';
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 getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_branch.graphql';
export default {
i18n: {
dropdownHeader: s__('Switch Branch'),
title: s__('Branches'),
fetchError: s__('Unable to fetch branch list for this project.'),
},
inputDebounce: BRANCH_SEARCH_DEBOUNCE,
components: {
GlDropdown,
GlDropdownItem,
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: {
branches: {
availableBranches: {
query: getAvailableBranches,
variables() {
return {
limit: this.page.limit,
offset: this.page.offset,
projectFullPath: this.projectFullPath,
searchPattern: this.searchPattern,
};
},
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() {
this.$emit('showError', {
......@@ -42,11 +85,37 @@ export default {
},
},
computed: {
hasBranchList() {
return this.branches?.length > 0;
isBranchesLoading() {
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: {
// 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) {
if (newBranch === this.currentBranch) {
return;
......@@ -62,24 +131,53 @@ export default {
this.$emit('refetchContent');
},
setSearchTerm(newSearchTerm) {
this.branches = [];
this.page = {
limit: newSearchTerm.trim() === '' ? this.paginationLimit : this.totalBranches,
offset: 0,
searchTerm: newSearchTerm.trim(),
};
},
},
};
</script>
<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>
{{ this.$options.i18n.title }}
{{ $options.i18n.title }}
</gl-dropdown-section-header>
<gl-infinite-scroll
:fetched-items="branches.length"
:total-items="totalBranches"
:max-list-height="250"
@bottomReached="fetchNextBranches"
>
<template #items>
<gl-dropdown-item
v-for="branch in branches"
:key="branch.name"
:is-checked="currentBranch === branch.name"
:key="branch"
:is-checked="currentBranch === branch"
:is-check-item="true"
@click="selectBranch(branch.name)"
@click="selectBranch(branch)"
>
<gl-icon name="check" class="gl-visibility-hidden" />
{{ branch.name }}
{{ 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>
</template>
......@@ -28,3 +28,6 @@ export const COMMIT_ACTION_CREATE = 'CREATE';
export const COMMIT_ACTION_UPDATE = 'UPDATE';
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!) {
project(fullPath: $projectFullPath) @client {
query getAvailableBranches(
$limit: Int!
$offset: Int!
$projectFullPath: ID!
$searchPattern: String!
) {
project(fullPath: $projectFullPath) {
repository {
branches {
name
}
branchNames(limit: $limit, offset: $offset, searchPattern: $searchPattern)
}
}
}
......@@ -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: {
lintCI: (_, { endpoint, content, dry_run }) => {
......
......@@ -43,6 +43,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
projectPath,
projectNamespace,
runnerHelpPagePath,
totalBranches,
ymlHelpPagePath,
} = el?.dataset;
......@@ -100,6 +101,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
projectPath,
projectNamespace,
runnerHelpPagePath,
totalBranches: parseInt(totalBranches, 10),
ymlHelpPagePath,
},
render(h) {
......
......@@ -27,6 +27,7 @@ module Ci
"project-full-path" => project.full_path,
"project-namespace" => project.namespace.full_path,
"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')
}
end
......
......@@ -31474,6 +31474,9 @@ msgstr ""
msgid "Survey Response"
msgstr ""
msgid "Switch Branch"
msgstr ""
msgid "Switch branch/tag"
msgstr ""
......
......@@ -9,7 +9,6 @@ import {
mockDefaultBranch,
mockLintResponse,
mockProjectFullPath,
mockProjectBranches,
} from '../mock_data';
jest.mock('~/api', () => {
......@@ -47,23 +46,6 @@ describe('~/pipeline_editor/graphql/resolvers', () => {
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', () => {
......
......@@ -139,18 +139,54 @@ export const mergeUnwrappedCiConfig = (mergedConfig) => {
};
export const mockProjectBranches = {
__typename: 'Project',
data: {
project: {
repository: {
__typename: 'Repository',
branches: [
{ __typename: 'Branch', name: 'main' },
{ __typename: 'Branch', name: 'develop' },
{ __typename: 'Branch', name: 'production' },
{ __typename: 'Branch', name: 'test' },
branchNames: [
'main',
'develop',
'production',
'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 = {
pipeline: {
commitPath: '/-/commit/aabbccdd',
......
......@@ -55,6 +55,7 @@ RSpec.describe Ci::PipelineEditorHelper do
"project-full-path" => project.full_path,
"project-namespace" => project.namespace.full_path,
"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')
})
end
......@@ -81,6 +82,7 @@ RSpec.describe Ci::PipelineEditorHelper do
"project-full-path" => project.full_path,
"project-namespace" => project.namespace.full_path,
"runner-help-page-path" => help_page_path('ci/runners/README'),
"total-branches" => 0,
"yml-help-page-path" => help_page_path('ci/yaml/README')
})
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