Commit c05acf6f authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '321790-load-refs-on-demand' into 'master'

Load refs for the "Run Pipeline" form on demand, limit to 100 results per ref type

See merge request gitlab-org/gitlab!55394
parents 592ad96c e5826739
...@@ -9,10 +9,6 @@ import { ...@@ -9,10 +9,6 @@ import {
GlFormSelect, GlFormSelect,
GlFormTextarea, GlFormTextarea,
GlLink, GlLink,
GlDropdown,
GlDropdownItem,
GlDropdownSectionHeader,
GlSearchBoxByType,
GlSprintf, GlSprintf,
GlLoadingIcon, GlLoadingIcon,
GlSafeHtmlDirective as SafeHtml, GlSafeHtmlDirective as SafeHtml,
...@@ -26,19 +22,26 @@ import httpStatusCodes from '~/lib/utils/http_status'; ...@@ -26,19 +22,26 @@ import httpStatusCodes from '~/lib/utils/http_status';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import { s__, __, n__ } from '~/locale'; import { s__, __, n__ } from '~/locale';
import { VARIABLE_TYPE, FILE_TYPE, CONFIG_VARIABLES_TIMEOUT } from '../constants'; import { VARIABLE_TYPE, FILE_TYPE, CONFIG_VARIABLES_TIMEOUT } from '../constants';
import RefsDropdown from './refs_dropdown.vue';
const i18n = {
variablesDescription: s__(
'Pipeline|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used by default.',
),
defaultError: __('Something went wrong on our end. Please try again.'),
refsLoadingErrorTitle: s__('Pipeline|Branches or tags could not be loaded.'),
submitErrorTitle: s__('Pipeline|Pipeline cannot be run.'),
warningTitle: __('The form contains the following warning:'),
maxWarningsSummary: __('%{total} warnings found: showing first %{warningsDisplayed}'),
};
export default { export default {
typeOptions: [ typeOptions: [
{ value: VARIABLE_TYPE, text: __('Variable') }, { value: VARIABLE_TYPE, text: __('Variable') },
{ value: FILE_TYPE, text: __('File') }, { value: FILE_TYPE, text: __('File') },
], ],
variablesDescription: s__( i18n,
'Pipeline|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used by default.',
),
formElementClasses: 'gl-mr-3 gl-mb-3 gl-flex-basis-quarter gl-flex-shrink-0 gl-flex-grow-0', formElementClasses: 'gl-mr-3 gl-mb-3 gl-flex-basis-quarter gl-flex-shrink-0 gl-flex-grow-0',
errorTitle: __('Pipeline cannot be run.'),
warningTitle: __('The form contains the following warning:'),
maxWarningsSummary: __('%{total} warnings found: showing first %{warningsDisplayed}'),
// this height value is used inline on the textarea to match the input field height // this height value is used inline on the textarea to match the input field height
// it's used to prevent the overwrite if 'gl-h-7' or 'gl-h-7!' were used // it's used to prevent the overwrite if 'gl-h-7' or 'gl-h-7!' were used
textAreaStyle: { height: '32px' }, textAreaStyle: { height: '32px' },
...@@ -52,12 +55,9 @@ export default { ...@@ -52,12 +55,9 @@ export default {
GlFormSelect, GlFormSelect,
GlFormTextarea, GlFormTextarea,
GlLink, GlLink,
GlDropdown,
GlDropdownItem,
GlDropdownSectionHeader,
GlSearchBoxByType,
GlSprintf, GlSprintf,
GlLoadingIcon, GlLoadingIcon,
RefsDropdown,
}, },
directives: { SafeHtml }, directives: { SafeHtml },
props: { props: {
...@@ -77,14 +77,6 @@ export default { ...@@ -77,14 +77,6 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
branches: {
type: Array,
required: true,
},
tags: {
type: Array,
required: true,
},
settingsLink: { settingsLink: {
type: String, type: String,
required: true, required: true,
...@@ -111,11 +103,11 @@ export default { ...@@ -111,11 +103,11 @@ export default {
}, },
data() { data() {
return { return {
searchTerm: '',
refValue: { refValue: {
shortName: this.refParam, shortName: this.refParam,
}, },
form: {}, form: {},
errorTitle: null,
error: null, error: null,
warnings: [], warnings: [],
totalWarnings: 0, totalWarnings: 0,
...@@ -125,22 +117,6 @@ export default { ...@@ -125,22 +117,6 @@ export default {
}; };
}, },
computed: { computed: {
lowerCasedSearchTerm() {
return this.searchTerm.toLowerCase();
},
filteredBranches() {
return this.branches.filter((branch) =>
branch.shortName.toLowerCase().includes(this.lowerCasedSearchTerm),
);
},
filteredTags() {
return this.tags.filter((tag) =>
tag.shortName.toLowerCase().includes(this.lowerCasedSearchTerm),
);
},
hasTags() {
return this.tags.length > 0;
},
overMaxWarningsLimit() { overMaxWarningsLimit() {
return this.totalWarnings > this.maxWarnings; return this.totalWarnings > this.maxWarnings;
}, },
...@@ -148,7 +124,7 @@ export default { ...@@ -148,7 +124,7 @@ export default {
return n__('%d warning found:', '%d warnings found:', this.warnings.length); return n__('%d warning found:', '%d warnings found:', this.warnings.length);
}, },
summaryMessage() { summaryMessage() {
return this.overMaxWarningsLimit ? this.$options.maxWarningsSummary : this.warningsSummary; return this.overMaxWarningsLimit ? i18n.maxWarningsSummary : this.warningsSummary;
}, },
shouldShowWarning() { shouldShowWarning() {
return this.warnings.length > 0 && !this.isWarningDismissed; return this.warnings.length > 0 && !this.isWarningDismissed;
...@@ -166,6 +142,11 @@ export default { ...@@ -166,6 +142,11 @@ export default {
return this.form[this.refFullName]?.descriptions ?? {}; return this.form[this.refFullName]?.descriptions ?? {};
}, },
}, },
watch: {
refValue() {
this.loadConfigVariablesForm();
},
},
created() { created() {
// this is needed until we add support for ref type in url query strings // this is needed until we add support for ref type in url query strings
// ensure default branch is called with full ref on load // ensure default branch is called with full ref on load
...@@ -174,7 +155,7 @@ export default { ...@@ -174,7 +155,7 @@ export default {
this.refValue.fullName = `refs/heads/${this.refValue.shortName}`; this.refValue.fullName = `refs/heads/${this.refValue.shortName}`;
} }
this.setRefSelected(this.refValue); this.loadConfigVariablesForm();
}, },
methods: { methods: {
addEmptyVariable(refValue) { addEmptyVariable(refValue) {
...@@ -213,10 +194,18 @@ export default { ...@@ -213,10 +194,18 @@ export default {
this.setVariable(refValue, type, key, value); this.setVariable(refValue, type, key, value);
}); });
}, },
setRefSelected(refValue) { removeVariable(index) {
this.refValue = refValue; this.variables.splice(index, 1);
},
canRemove(index) {
return index < this.variables.length - 1;
},
loadConfigVariablesForm() {
// Skip when variables already cached in `form`
if (this.form[this.refFullName]) {
return;
}
if (!this.form[this.refFullName]) {
this.fetchConfigVariables(this.refFullName || this.refShortName) this.fetchConfigVariables(this.refFullName || this.refShortName)
.then(({ descriptions, params }) => { .then(({ descriptions, params }) => {
Vue.set(this.form, this.refFullName, { Vue.set(this.form, this.refFullName, {
...@@ -245,16 +234,6 @@ export default { ...@@ -245,16 +234,6 @@ export default {
// Adds empty var at the end of the form // Adds empty var at the end of the form
this.addEmptyVariable(this.refFullName); this.addEmptyVariable(this.refFullName);
}); });
}
},
isSelected(ref) {
return ref.fullName === this.refValue.fullName;
},
removeVariable(index) {
this.variables.splice(index, 1);
},
canRemove(index) {
return index < this.variables.length - 1;
}, },
fetchConfigVariables(refValue) { fetchConfigVariables(refValue) {
this.isLoading = true; this.isLoading = true;
...@@ -330,10 +309,24 @@ export default { ...@@ -330,10 +309,24 @@ export default {
} = err?.response?.data; } = err?.response?.data;
const [error] = errors; const [error] = errors;
this.reportError({
title: i18n.submitErrorTitle,
error,
warnings,
totalWarnings,
});
});
},
onRefsLoadingError(error) {
this.reportError({ title: i18n.refsLoadingErrorTitle });
Sentry.captureException(error);
},
reportError({ title = null, error = i18n.defaultError, warnings = [], totalWarnings = 0 }) {
this.errorTitle = title;
this.error = error; this.error = error;
this.warnings = warnings; this.warnings = warnings;
this.totalWarnings = totalWarnings; this.totalWarnings = totalWarnings;
});
}, },
}, },
}; };
...@@ -343,7 +336,7 @@ export default { ...@@ -343,7 +336,7 @@ export default {
<gl-form @submit.prevent="createPipeline"> <gl-form @submit.prevent="createPipeline">
<gl-alert <gl-alert
v-if="error" v-if="error"
:title="$options.errorTitle" :title="errorTitle"
:dismissible="false" :dismissible="false"
variant="danger" variant="danger"
class="gl-mb-4" class="gl-mb-4"
...@@ -353,7 +346,7 @@ export default { ...@@ -353,7 +346,7 @@ export default {
</gl-alert> </gl-alert>
<gl-alert <gl-alert
v-if="shouldShowWarning" v-if="shouldShowWarning"
:title="$options.warningTitle" :title="$options.i18n.warningTitle"
variant="warning" variant="warning"
class="gl-mb-4" class="gl-mb-4"
data-testid="run-pipeline-warning-alert" data-testid="run-pipeline-warning-alert"
...@@ -380,31 +373,7 @@ export default { ...@@ -380,31 +373,7 @@ export default {
</details> </details>
</gl-alert> </gl-alert>
<gl-form-group :label="s__('Pipeline|Run for branch name or tag')"> <gl-form-group :label="s__('Pipeline|Run for branch name or tag')">
<gl-dropdown :text="refShortName" block> <refs-dropdown v-model="refValue" @loadingError="onRefsLoadingError" />
<gl-search-box-by-type v-model.trim="searchTerm" :placeholder="__('Search refs')" />
<gl-dropdown-section-header>{{ __('Branches') }}</gl-dropdown-section-header>
<gl-dropdown-item
v-for="branch in filteredBranches"
:key="branch.fullName"
class="gl-font-monospace"
is-check-item
:is-checked="isSelected(branch)"
@click="setRefSelected(branch)"
>
{{ branch.shortName }}
</gl-dropdown-item>
<gl-dropdown-section-header v-if="hasTags">{{ __('Tags') }}</gl-dropdown-section-header>
<gl-dropdown-item
v-for="tag in filteredTags"
:key="tag.fullName"
class="gl-font-monospace"
is-check-item
:is-checked="isSelected(tag)"
@click="setRefSelected(tag)"
>
{{ tag.shortName }}
</gl-dropdown-item>
</gl-dropdown>
</gl-form-group> </gl-form-group>
<gl-loading-icon v-if="isLoading" class="gl-mb-5" size="lg" /> <gl-loading-icon v-if="isLoading" class="gl-mb-5" size="lg" />
...@@ -465,7 +434,7 @@ export default { ...@@ -465,7 +434,7 @@ export default {
</div> </div>
<template #description <template #description
><gl-sprintf :message="$options.variablesDescription"> ><gl-sprintf :message="$options.i18n.variablesDescription">
<template #link="{ content }"> <template #link="{ content }">
<gl-link :href="settingsLink">{{ content }}</gl-link> <gl-link :href="settingsLink">{{ content }}</gl-link>
</template> </template>
......
<script>
import { GlDropdown, GlDropdownItem, GlDropdownSectionHeader, GlSearchBoxByType } from '@gitlab/ui';
import { debounce } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { BRANCH_REF_TYPE, TAG_REF_TYPE, DEBOUNCE_REFS_SEARCH_MS } from '../constants';
import formatRefs from '../utils/format_refs';
export default {
components: {
GlDropdown,
GlDropdownItem,
GlDropdownSectionHeader,
GlSearchBoxByType,
},
inject: ['projectRefsEndpoint'],
props: {
value: {
type: Object,
required: false,
default: () => ({}),
},
},
data() {
return {
isLoading: false,
searchTerm: '',
branches: [],
tags: [],
};
},
computed: {
lowerCasedSearchTerm() {
return this.searchTerm.toLowerCase();
},
refShortName() {
return this.value.shortName;
},
hasTags() {
return this.tags.length > 0;
},
},
watch: {
searchTerm() {
this.debouncedLoadRefs();
},
},
methods: {
loadRefs() {
this.isLoading = true;
axios
.get(this.projectRefsEndpoint, {
params: {
search: this.lowerCasedSearchTerm,
},
})
.then(({ data }) => {
// Note: These keys are uppercase in API
const { Branches = [], Tags = [] } = data;
this.branches = formatRefs(Branches, BRANCH_REF_TYPE);
this.tags = formatRefs(Tags, TAG_REF_TYPE);
})
.catch((e) => {
this.$emit('loadingError', e);
})
.finally(() => {
this.isLoading = false;
});
},
debouncedLoadRefs: debounce(function debouncedLoadRefs() {
this.loadRefs();
}, DEBOUNCE_REFS_SEARCH_MS),
setRefSelected(ref) {
this.$emit('input', ref);
},
isSelected(ref) {
return ref.fullName === this.value.fullName;
},
},
};
</script>
<template>
<gl-dropdown :text="refShortName" block @show.once="loadRefs">
<gl-search-box-by-type
v-model.trim="searchTerm"
:is-loading="isLoading"
:placeholder="__('Search refs')"
/>
<gl-dropdown-section-header>{{ __('Branches') }}</gl-dropdown-section-header>
<gl-dropdown-item
v-for="branch in branches"
:key="branch.fullName"
class="gl-font-monospace"
is-check-item
:is-checked="isSelected(branch)"
@click="setRefSelected(branch)"
>
{{ branch.shortName }}
</gl-dropdown-item>
<gl-dropdown-section-header v-if="hasTags">{{ __('Tags') }}</gl-dropdown-section-header>
<gl-dropdown-item
v-for="tag in tags"
:key="tag.fullName"
class="gl-font-monospace"
is-check-item
:is-checked="isSelected(tag)"
@click="setRefSelected(tag)"
>
{{ tag.shortName }}
</gl-dropdown-item>
</gl-dropdown>
</template>
export const VARIABLE_TYPE = 'env_var'; export const VARIABLE_TYPE = 'env_var';
export const FILE_TYPE = 'file'; export const FILE_TYPE = 'file';
export const DEBOUNCE_REFS_SEARCH_MS = 250;
export const CONFIG_VARIABLES_TIMEOUT = 5000; export const CONFIG_VARIABLES_TIMEOUT = 5000;
export const BRANCH_REF_TYPE = 'branch'; export const BRANCH_REF_TYPE = 'branch';
export const TAG_REF_TYPE = 'tag'; export const TAG_REF_TYPE = 'tag';
import Vue from 'vue'; import Vue from 'vue';
import PipelineNewForm from './components/pipeline_new_form.vue'; import PipelineNewForm from './components/pipeline_new_form.vue';
import formatRefs from './utils/format_refs';
export default () => { export default () => {
const el = document.getElementById('js-new-pipeline'); const el = document.getElementById('js-new-pipeline');
const { const {
// provide/inject
projectRefsEndpoint,
// props
projectId, projectId,
pipelinesPath, pipelinesPath,
configVariablesPath, configVariablesPath,
...@@ -12,19 +15,18 @@ export default () => { ...@@ -12,19 +15,18 @@ export default () => {
refParam, refParam,
varParam, varParam,
fileParam, fileParam,
branchRefs,
tagRefs,
settingsLink, settingsLink,
maxWarnings, maxWarnings,
} = el?.dataset; } = el?.dataset;
const variableParams = JSON.parse(varParam); const variableParams = JSON.parse(varParam);
const fileParams = JSON.parse(fileParam); const fileParams = JSON.parse(fileParam);
const branches = formatRefs(JSON.parse(branchRefs), 'branch');
const tags = formatRefs(JSON.parse(tagRefs), 'tag');
return new Vue({ return new Vue({
el, el,
provide: {
projectRefsEndpoint,
},
render(createElement) { render(createElement) {
return createElement(PipelineNewForm, { return createElement(PipelineNewForm, {
props: { props: {
...@@ -35,8 +37,6 @@ export default () => { ...@@ -35,8 +37,6 @@ export default () => {
refParam, refParam,
variableParams, variableParams,
fileParams, fileParams,
branches,
tags,
settingsLink, settingsLink,
maxWarnings: Number(maxWarnings), maxWarnings: Number(maxWarnings),
}, },
......
...@@ -14,8 +14,7 @@ ...@@ -14,8 +14,7 @@
ref_param: params[:ref] || @project.default_branch, ref_param: params[:ref] || @project.default_branch,
var_param: params[:var].to_json, var_param: params[:var].to_json,
file_param: params[:file_var].to_json, file_param: params[:file_var].to_json,
branch_refs: @project.repository.branch_names.to_json.html_safe, project_refs_endpoint: refs_project_path(@project, sort: 'updated_desc'),
tag_refs: @project.repository.tag_names.to_json.html_safe,
settings_link: project_settings_ci_cd_path(@project), settings_link: project_settings_ci_cd_path(@project),
max_warnings: ::Gitlab::Ci::Warnings::MAX_LIMIT } } max_warnings: ::Gitlab::Ci::Warnings::MAX_LIMIT } }
......
---
title: Improve performance of manual pipeline form by limiting the refs loaded on page load.
merge_request: 55394
author:
type: performance
...@@ -137,10 +137,10 @@ To execute a pipeline manually: ...@@ -137,10 +137,10 @@ To execute a pipeline manually:
1. Navigate to your project's **CI/CD > Pipelines**. 1. Navigate to your project's **CI/CD > Pipelines**.
1. Select the **Run Pipeline** button. 1. Select the **Run Pipeline** button.
1. On the **Run Pipeline** page: 1. On the **Run Pipeline** page:
1. Select the branch to run the pipeline for in the **Create for** field. 1. Select the branch or tag to run the pipeline for in the **Run for branch name or tag** field.
1. Enter any [environment variables](../variables/README.md) required for the pipeline run. 1. Enter any [environment variables](../variables/README.md) required for the pipeline run.
You can set specific variables to have their [values prefilled in the form](#prefill-variables-in-manual-pipelines). You can set specific variables to have their [values prefilled in the form](#prefill-variables-in-manual-pipelines).
1. Click the **Create pipeline** button. 1. Click the **Run pipeline** button.
The pipeline now executes the jobs as configured. The pipeline now executes the jobs as configured.
......
...@@ -22028,9 +22028,6 @@ msgstr "" ...@@ -22028,9 +22028,6 @@ msgstr ""
msgid "Pipeline Schedules" msgid "Pipeline Schedules"
msgstr "" msgstr ""
msgid "Pipeline cannot be run."
msgstr ""
msgid "Pipeline minutes quota" msgid "Pipeline minutes quota"
msgstr "" msgstr ""
...@@ -22289,6 +22286,9 @@ msgstr "" ...@@ -22289,6 +22286,9 @@ msgstr ""
msgid "Pipeline|Branch name" msgid "Pipeline|Branch name"
msgstr "" msgstr ""
msgid "Pipeline|Branches or tags could not be loaded."
msgstr ""
msgid "Pipeline|Canceled" msgid "Pipeline|Canceled"
msgstr "" msgstr ""
...@@ -22349,6 +22349,9 @@ msgstr "" ...@@ -22349,6 +22349,9 @@ msgstr ""
msgid "Pipeline|Pipeline %{idStart}#%{idEnd} %{statusStart}%{statusEnd} for %{commitStart}%{commitEnd}" msgid "Pipeline|Pipeline %{idStart}#%{idEnd} %{statusStart}%{statusEnd} for %{commitStart}%{commitEnd}"
msgstr "" msgstr ""
msgid "Pipeline|Pipeline cannot be run."
msgstr ""
msgid "Pipeline|Pipelines" msgid "Pipeline|Pipelines"
msgstr "" msgstr ""
......
import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
import RefsDropdown from '~/pipeline_new/components/refs_dropdown.vue';
import { mockRefs, mockFilteredRefs } from '../mock_data';
const projectRefsEndpoint = '/root/project/refs';
const refShortName = 'master';
const refFullName = 'refs/heads/master';
jest.mock('~/flash');
describe('Pipeline New Form', () => {
let wrapper;
let mock;
const findDropdown = () => wrapper.find(GlDropdown);
const findRefsDropdownItems = () => wrapper.findAll(GlDropdownItem);
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
const createComponent = (props = {}, mountFn = shallowMount) => {
wrapper = mountFn(RefsDropdown, {
provide: {
projectRefsEndpoint,
},
propsData: {
value: {
shortName: refShortName,
fullName: refFullName,
},
...props,
},
});
};
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet(projectRefsEndpoint, { params: { search: '' } }).reply(httpStatusCodes.OK, mockRefs);
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
mock.restore();
});
beforeEach(() => {
createComponent();
});
it('displays empty dropdown initially', async () => {
await findDropdown().vm.$emit('show');
expect(findRefsDropdownItems()).toHaveLength(0);
});
it('does not make requests immediately', async () => {
expect(mock.history.get).toHaveLength(0);
});
describe('when user opens dropdown', () => {
beforeEach(async () => {
await findDropdown().vm.$emit('show');
await waitForPromises();
});
it('requests unfiltered tags and branches', async () => {
expect(mock.history.get).toHaveLength(1);
expect(mock.history.get[0].url).toBe(projectRefsEndpoint);
expect(mock.history.get[0].params).toEqual({ search: '' });
});
it('displays dropdown with branches and tags', async () => {
const refLength = mockRefs.Tags.length + mockRefs.Branches.length;
expect(findRefsDropdownItems()).toHaveLength(refLength);
});
it('displays the names of refs', () => {
// Branches
expect(findRefsDropdownItems().at(0).text()).toBe(mockRefs.Branches[0]);
// Tags (appear after branches)
const firstTag = mockRefs.Branches.length;
expect(findRefsDropdownItems().at(firstTag).text()).toBe(mockRefs.Tags[0]);
});
it('when user shows dropdown a second time, only one request is done', () => {
expect(mock.history.get).toHaveLength(1);
});
describe('when user selects a value', () => {
const selectedIndex = 1;
beforeEach(async () => {
await findRefsDropdownItems().at(selectedIndex).vm.$emit('click');
});
it('component emits @input', () => {
const inputs = wrapper.emitted('input');
expect(inputs).toHaveLength(1);
expect(inputs[0]).toEqual([{ shortName: 'branch-1', fullName: 'refs/heads/branch-1' }]);
});
});
describe('when user types searches for a tag', () => {
const mockSearchTerm = 'my-search';
beforeEach(async () => {
mock
.onGet(projectRefsEndpoint, { params: { search: mockSearchTerm } })
.reply(httpStatusCodes.OK, mockFilteredRefs);
await findSearchBox().vm.$emit('input', mockSearchTerm);
await waitForPromises();
});
it('requests filtered tags and branches', async () => {
expect(mock.history.get).toHaveLength(2);
expect(mock.history.get[1].params).toEqual({
search: mockSearchTerm,
});
});
it('displays dropdown with branches and tags', async () => {
const filteredRefLength = mockFilteredRefs.Tags.length + mockFilteredRefs.Branches.length;
expect(findRefsDropdownItems()).toHaveLength(filteredRefLength);
});
});
});
describe('when user has selected a value', () => {
const selectedIndex = 1;
const mockShortName = mockRefs.Branches[selectedIndex];
const mockFullName = `refs/heads/${mockShortName}`;
beforeEach(async () => {
mock
.onGet(projectRefsEndpoint, {
params: { ref: mockFullName },
})
.reply(httpStatusCodes.OK, mockRefs);
createComponent({
value: {
shortName: mockShortName,
fullName: mockFullName,
},
});
await findDropdown().vm.$emit('show');
await waitForPromises();
});
it('branch is checked', () => {
expect(findRefsDropdownItems().at(selectedIndex).props('isChecked')).toBe(true);
});
});
describe('when server returns an error', () => {
beforeEach(async () => {
mock
.onGet(projectRefsEndpoint, { params: { search: '' } })
.reply(httpStatusCodes.INTERNAL_SERVER_ERROR);
await findDropdown().vm.$emit('show');
await waitForPromises();
});
it('loading error event is emitted', () => {
expect(wrapper.emitted('loadingError')).toHaveLength(1);
expect(wrapper.emitted('loadingError')[0]).toEqual([expect.any(Error)]);
});
});
});
export const mockBranches = [ export const mockRefs = {
{ shortName: 'master', fullName: 'refs/heads/master' }, Branches: ['master', 'branch-1', 'branch-2'],
{ shortName: 'branch-1', fullName: 'refs/heads/branch-1' }, Tags: ['1.0.0', '1.1.0', '1.2.0'],
{ shortName: 'branch-2', fullName: 'refs/heads/branch-2' }, };
];
export const mockTags = [ export const mockFilteredRefs = {
{ shortName: '1.0.0', fullName: 'refs/tags/1.0.0' }, Branches: ['branch-1'],
{ shortName: '1.1.0', fullName: 'refs/tags/1.1.0' }, Tags: ['1.0.0', '1.1.0'],
{ shortName: '1.2.0', fullName: 'refs/tags/1.2.0' }, };
];
export const mockParams = { export const mockQueryParams = {
refParam: 'tag-1', refParam: 'tag-1',
variableParams: { variableParams: {
test_var: 'test_var_val', test_var: 'test_var_val',
......
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