Commit e7bf6f62 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'jivanvl-add-show-pipeline-iid-dropdown' into 'master'

Add pipeline key selection dropdown [RUN-AS-IF-FOSS]

See merge request gitlab-org/gitlab!67625
parents 2d4df984 0052c811
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import { GlButton, GlEmptyState, GlLoadingIcon, GlModal, GlLink } from '@gitlab/ui'; import { GlButton, GlEmptyState, GlLoadingIcon, GlModal, GlLink } from '@gitlab/ui';
import { getParameterByName } from '~/lib/utils/url_utility'; import { getParameterByName } from '~/lib/utils/url_utility';
import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue'; import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
import { PipelineKeyOptions } from '~/pipelines/constants';
import eventHub from '~/pipelines/event_hub'; import eventHub from '~/pipelines/event_hub';
import PipelinesMixin from '~/pipelines/mixins/pipelines_mixin'; import PipelinesMixin from '~/pipelines/mixins/pipelines_mixin';
import PipelinesService from '~/pipelines/services/pipelines_service'; import PipelinesService from '~/pipelines/services/pipelines_service';
...@@ -10,6 +11,7 @@ import TablePagination from '~/vue_shared/components/pagination/table_pagination ...@@ -10,6 +11,7 @@ import TablePagination from '~/vue_shared/components/pagination/table_pagination
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default { export default {
PipelineKeyOptions,
components: { components: {
GlButton, GlButton,
GlEmptyState, GlEmptyState,
...@@ -205,6 +207,7 @@ export default { ...@@ -205,6 +207,7 @@ export default {
:pipelines="state.pipelines" :pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown" :update-graph-dropdown="updateGraphDropdown"
:view-type="viewType" :view-type="viewType"
:pipeline-key-option="$options.PipelineKeyOptions[0]"
> >
<template #table-header-actions> <template #table-header-actions>
<div v-if="canRenderPipelineButton" class="gl-text-right"> <div v-if="canRenderPipelineButton" class="gl-text-right">
......
...@@ -29,6 +29,10 @@ export default { ...@@ -29,6 +29,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
pipelineKey: {
type: String,
required: true,
},
}, },
computed: { computed: {
user() { user() {
...@@ -60,7 +64,7 @@ export default { ...@@ -60,7 +64,7 @@ export default {
data-testid="pipeline-url-link" data-testid="pipeline-url-link"
data-qa-selector="pipeline_url_link" data-qa-selector="pipeline_url_link"
> >
#{{ pipeline.id }} #{{ pipeline[pipelineKey] }}
</gl-link> </gl-link>
<div class="label-container"> <div class="label-container">
<gl-badge <gl-badge
......
<script> <script>
import { GlEmptyState, GlIcon, GlLoadingIcon } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem, GlEmptyState, GlIcon, GlLoadingIcon } from '@gitlab/ui';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { getParameterByName } from '~/lib/utils/url_utility'; import { getParameterByName } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue'; import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING, FILTER_TAG_IDENTIFIER } from '../../constants'; import {
ANY_TRIGGER_AUTHOR,
RAW_TEXT_WARNING,
FILTER_TAG_IDENTIFIER,
PipelineKeyOptions,
} from '../../constants';
import PipelinesMixin from '../../mixins/pipelines_mixin'; import PipelinesMixin from '../../mixins/pipelines_mixin';
import PipelinesService from '../../services/pipelines_service'; import PipelinesService from '../../services/pipelines_service';
import { validateParams } from '../../utils'; import { validateParams } from '../../utils';
...@@ -16,8 +21,11 @@ import PipelinesFilteredSearch from './pipelines_filtered_search.vue'; ...@@ -16,8 +21,11 @@ import PipelinesFilteredSearch from './pipelines_filtered_search.vue';
import PipelinesTableComponent from './pipelines_table.vue'; import PipelinesTableComponent from './pipelines_table.vue';
export default { export default {
PipelineKeyOptions,
components: { components: {
EmptyState, EmptyState,
GlDropdown,
GlDropdownItem,
GlEmptyState, GlEmptyState,
GlIcon, GlIcon,
GlLoadingIcon, GlLoadingIcon,
...@@ -114,6 +122,7 @@ export default { ...@@ -114,6 +122,7 @@ export default {
page: getParameterByName('page') || '1', page: getParameterByName('page') || '1',
requestData: {}, requestData: {},
isResetCacheButtonLoading: false, isResetCacheButtonLoading: false,
selectedPipelineKeyOption: this.$options.PipelineKeyOptions[0],
}; };
}, },
stateMap: { stateMap: {
...@@ -301,6 +310,9 @@ export default { ...@@ -301,6 +310,9 @@ export default {
this.updateContent(this.requestData); this.updateContent(this.requestData);
}, },
changeVisibilityPipelineID(val) {
this.selectedPipelineKeyOption = val;
},
}, },
}; };
</script> </script>
...@@ -330,12 +342,31 @@ export default { ...@@ -330,12 +342,31 @@ export default {
/> />
</div> </div>
<pipelines-filtered-search <div v-if="stateToRender !== $options.stateMap.emptyState" class="gl-display-flex">
v-if="stateToRender !== $options.stateMap.emptyState" <div class="row-content-block gl-display-flex gl-flex-grow-1">
:project-id="projectId" <pipelines-filtered-search
:params="validatedParams" class="gl-display-flex gl-flex-grow-1 gl-mr-4"
@filterPipelines="filterPipelines" :project-id="projectId"
/> :params="validatedParams"
@filterPipelines="filterPipelines"
/>
<gl-dropdown
class="gl-display-flex"
:text="selectedPipelineKeyOption.text"
data-testid="pipeline-key-dropdown"
>
<gl-dropdown-item
v-for="(val, index) in $options.PipelineKeyOptions"
:key="index"
:is-checked="selectedPipelineKeyOption.key === val.key"
is-check-item
@click="changeVisibilityPipelineID(val)"
>
{{ val.text }}
</gl-dropdown-item>
</gl-dropdown>
</div>
</div>
<div class="content-list pipelines"> <div class="content-list pipelines">
<gl-loading-icon <gl-loading-icon
...@@ -374,6 +405,7 @@ export default { ...@@ -374,6 +405,7 @@ export default {
:pipeline-schedule-url="pipelineScheduleUrl" :pipeline-schedule-url="pipelineScheduleUrl"
:update-graph-dropdown="updateGraphDropdown" :update-graph-dropdown="updateGraphDropdown"
:view-type="viewType" :view-type="viewType"
:pipeline-key-option="selectedPipelineKeyOption"
/> />
</div> </div>
......
...@@ -101,12 +101,10 @@ export default { ...@@ -101,12 +101,10 @@ export default {
</script> </script>
<template> <template>
<div class="row-content-block"> <gl-filtered-search
<gl-filtered-search v-model="value"
v-model="value" :placeholder="__('Filter pipelines')"
:placeholder="__('Filter pipelines')" :available-tokens="tokens"
:available-tokens="tokens" @submit="onSubmit"
@submit="onSubmit" />
/>
</div>
</template> </template>
...@@ -17,63 +17,6 @@ const DEFAULT_TH_CLASSES = ...@@ -17,63 +17,6 @@ const DEFAULT_TH_CLASSES =
'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1! gl-font-sm!'; 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1! gl-font-sm!';
export default { export default {
fields: [
{
key: 'status',
label: s__('Pipeline|Status'),
thClass: DEFAULT_TH_CLASSES,
columnClass: 'gl-w-10p',
tdClass: DEFAULT_TD_CLASS,
thAttr: { 'data-testid': 'status-th' },
},
{
key: 'pipeline',
label: s__('Pipeline|Pipeline'),
thClass: DEFAULT_TH_CLASSES,
tdClass: `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
columnClass: 'gl-w-10p',
thAttr: { 'data-testid': 'pipeline-th' },
},
{
key: 'triggerer',
label: s__('Pipeline|Triggerer'),
thClass: DEFAULT_TH_CLASSES,
tdClass: `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
columnClass: 'gl-w-10p',
thAttr: { 'data-testid': 'triggerer-th' },
},
{
key: 'commit',
label: s__('Pipeline|Commit'),
thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS,
columnClass: 'gl-w-20p',
thAttr: { 'data-testid': 'commit-th' },
},
{
key: 'stages',
label: s__('Pipeline|Stages'),
thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS,
columnClass: 'gl-w-quarter',
thAttr: { 'data-testid': 'stages-th' },
},
{
key: 'timeago',
label: s__('Pipeline|Duration'),
thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS,
columnClass: 'gl-w-15p',
thAttr: { 'data-testid': 'timeago-th' },
},
{
key: 'actions',
thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS,
columnClass: 'gl-w-15p',
thAttr: { 'data-testid': 'actions-th' },
},
],
components: { components: {
GlTable, GlTable,
LinkedPipelinesMiniList: () => LinkedPipelinesMiniList: () =>
...@@ -109,6 +52,10 @@ export default { ...@@ -109,6 +52,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
pipelineKeyOption: {
type: Object,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -118,6 +65,68 @@ export default { ...@@ -118,6 +65,68 @@ export default {
cancelingPipeline: null, cancelingPipeline: null,
}; };
}, },
computed: {
tableFields() {
const fields = [
{
key: 'status',
label: s__('Pipeline|Status'),
thClass: DEFAULT_TH_CLASSES,
columnClass: 'gl-w-10p',
tdClass: DEFAULT_TD_CLASS,
thAttr: { 'data-testid': 'status-th' },
},
{
key: 'pipeline',
label: this.pipelineKeyOption.label,
thClass: DEFAULT_TH_CLASSES,
tdClass: `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
columnClass: 'gl-w-10p',
thAttr: { 'data-testid': 'pipeline-th' },
},
{
key: 'triggerer',
label: s__('Pipeline|Triggerer'),
thClass: DEFAULT_TH_CLASSES,
tdClass: `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
columnClass: 'gl-w-10p',
thAttr: { 'data-testid': 'triggerer-th' },
},
{
key: 'commit',
label: s__('Pipeline|Commit'),
thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS,
columnClass: 'gl-w-20p',
thAttr: { 'data-testid': 'commit-th' },
},
{
key: 'stages',
label: s__('Pipeline|Stages'),
thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS,
columnClass: 'gl-w-quarter',
thAttr: { 'data-testid': 'stages-th' },
},
{
key: 'timeago',
label: s__('Pipeline|Duration'),
thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS,
columnClass: 'gl-w-15p',
thAttr: { 'data-testid': 'timeago-th' },
},
{
key: 'actions',
thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS,
columnClass: 'gl-w-15p',
thAttr: { 'data-testid': 'actions-th' },
},
];
return fields;
},
},
watch: { watch: {
pipelines() { pipelines() {
this.cancelingPipeline = null; this.cancelingPipeline = null;
...@@ -148,7 +157,7 @@ export default { ...@@ -148,7 +157,7 @@ export default {
<template> <template>
<div class="ci-table"> <div class="ci-table">
<gl-table <gl-table
:fields="$options.fields" :fields="tableFields"
:items="pipelines" :items="pipelines"
tbody-tr-class="commit" tbody-tr-class="commit"
:tbody-tr-attr="{ 'data-testid': 'pipeline-table-row' }" :tbody-tr-attr="{ 'data-testid': 'pipeline-table-row' }"
...@@ -169,7 +178,11 @@ export default { ...@@ -169,7 +178,11 @@ export default {
</template> </template>
<template #cell(pipeline)="{ item }"> <template #cell(pipeline)="{ item }">
<pipeline-url :pipeline="item" :pipeline-schedule-url="pipelineScheduleUrl" /> <pipeline-url
:pipeline="item"
:pipeline-schedule-url="pipelineScheduleUrl"
:pipeline-key="pipelineKeyOption.key"
/>
</template> </template>
<template #cell(triggerer)="{ item }"> <template #cell(triggerer)="{ item }">
......
...@@ -35,3 +35,17 @@ export const POST_FAILURE = 'post_failure'; ...@@ -35,3 +35,17 @@ export const POST_FAILURE = 'post_failure';
export const UNSUPPORTED_DATA = 'unsupported_data'; export const UNSUPPORTED_DATA = 'unsupported_data';
export const CHILD_VIEW = 'child'; export const CHILD_VIEW = 'child';
// Constants for the ID and IID selection dropdown
export const PipelineKeyOptions = [
{
text: __('Show Pipeline ID'),
label: __('Pipeline ID'),
key: 'id',
},
{
text: __('Show Pipeline IID'),
label: __('Pipeline IID'),
key: 'iid',
},
];
...@@ -123,6 +123,9 @@ you can filter the pipeline list by: ...@@ -123,6 +123,9 @@ you can filter the pipeline list by:
- Status ([GitLab 13.1 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/217617)) - Status ([GitLab 13.1 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/217617))
- Tag ([GitLab 13.1 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/217617)) - Tag ([GitLab 13.1 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/217617))
[Starting in GitLab 14.2](https://gitlab.com/gitlab-org/gitlab/-/issues/26621), you can change the
pipeline column to display the pipeline ID or the pipeline IID.
### Run a pipeline manually ### Run a pipeline manually
Pipelines can be manually executed, with predefined or manually-specified [variables](../variables/index.md). Pipelines can be manually executed, with predefined or manually-specified [variables](../variables/index.md).
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue'; import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
import { PipelineKeyOptions } from '~/pipelines/constants';
import { triggeredBy, triggered } from './mock_data'; import { triggeredBy, triggered } from './mock_data';
...@@ -15,6 +16,7 @@ describe('Pipelines Table', () => { ...@@ -15,6 +16,7 @@ describe('Pipelines Table', () => {
const defaultProps = { const defaultProps = {
pipelines: [], pipelines: [],
viewType: 'root', viewType: 'root',
pipelineKeyOption: PipelineKeyOptions[0],
}; };
const createMockPipeline = () => { const createMockPipeline = () => {
......
...@@ -24174,6 +24174,12 @@ msgstr "" ...@@ -24174,6 +24174,12 @@ msgstr ""
msgid "Pipeline %{label} for \"%{dataTitle}\"" msgid "Pipeline %{label} for \"%{dataTitle}\""
msgstr "" msgstr ""
msgid "Pipeline ID"
msgstr ""
msgid "Pipeline IID"
msgstr ""
msgid "Pipeline Schedule" msgid "Pipeline Schedule"
msgstr "" msgstr ""
...@@ -30435,6 +30441,12 @@ msgstr "" ...@@ -30435,6 +30441,12 @@ msgstr ""
msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{boldStart}will%{boldEnd} lose access to your account." msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{boldStart}will%{boldEnd} lose access to your account."
msgstr "" msgstr ""
msgid "Show Pipeline ID"
msgstr ""
msgid "Show Pipeline IID"
msgstr ""
msgid "Show all activity" msgid "Show all activity"
msgstr "" msgstr ""
......
...@@ -585,6 +585,26 @@ RSpec.describe 'Pipelines', :js do ...@@ -585,6 +585,26 @@ RSpec.describe 'Pipelines', :js do
expect(page).to have_selector('.gl-pagination .page-link', count: 4) expect(page).to have_selector('.gl-pagination .page-link', count: 4)
end end
end end
context 'with pipeline key selection' do
before do
visit project_pipelines_path(project)
wait_for_requests
end
it 'changes the Pipeline ID column for Pipeline IID' do
page.find('[data-testid="pipeline-key-dropdown"]').click
within '.gl-new-dropdown-contents' do
dropdown_options = page.find_all '.gl-new-dropdown-item'
dropdown_options[1].click
end
expect(page.find('[data-testid="pipeline-th"]')).to have_content 'Pipeline IID'
expect(page.find('[data-testid="pipeline-url-link"]')).to have_content "##{pipeline.iid}"
end
end
end end
describe 'GET /:project/-/pipelines/show' do describe 'GET /:project/-/pipelines/show' do
......
...@@ -28,6 +28,7 @@ describe('Pipeline Url Component', () => { ...@@ -28,6 +28,7 @@ describe('Pipeline Url Component', () => {
flags: {}, flags: {},
}, },
pipelineScheduleUrl: 'foo', pipelineScheduleUrl: 'foo',
pipelineKey: 'id',
}; };
const createComponent = (props) => { const createComponent = (props) => {
......
...@@ -74,6 +74,7 @@ describe('Pipelines', () => { ...@@ -74,6 +74,7 @@ describe('Pipelines', () => {
const findTablePagination = () => wrapper.findComponent(TablePagination); const findTablePagination = () => wrapper.findComponent(TablePagination);
const findTab = (tab) => wrapper.findByTestId(`pipelines-tab-${tab}`); const findTab = (tab) => wrapper.findByTestId(`pipelines-tab-${tab}`);
const findPipelineKeyDropdown = () => wrapper.findByTestId('pipeline-key-dropdown');
const findRunPipelineButton = () => wrapper.findByTestId('run-pipeline-button'); const findRunPipelineButton = () => wrapper.findByTestId('run-pipeline-button');
const findCiLintButton = () => wrapper.findByTestId('ci-lint-button'); const findCiLintButton = () => wrapper.findByTestId('ci-lint-button');
const findCleanCacheButton = () => wrapper.findByTestId('clear-cache-button'); const findCleanCacheButton = () => wrapper.findByTestId('clear-cache-button');
...@@ -528,6 +529,10 @@ describe('Pipelines', () => { ...@@ -528,6 +529,10 @@ describe('Pipelines', () => {
expect(findFilteredSearch().exists()).toBe(true); expect(findFilteredSearch().exists()).toBe(true);
}); });
it('renders the pipeline key dropdown', () => {
expect(findPipelineKeyDropdown().exists()).toBe(true);
});
it('renders tab empty state finished scope', async () => { it('renders tab empty state finished scope', async () => {
mock.onGet(mockPipelinesEndpoint, { params: { scope: 'finished', page: '1' } }).reply(200, { mock.onGet(mockPipelinesEndpoint, { params: { scope: 'finished', page: '1' } }).reply(200, {
pipelines: [], pipelines: [],
...@@ -623,6 +628,10 @@ describe('Pipelines', () => { ...@@ -623,6 +628,10 @@ describe('Pipelines', () => {
expect(findFilteredSearch().exists()).toBe(false); expect(findFilteredSearch().exists()).toBe(false);
}); });
it('does not render the pipeline key dropdown', () => {
expect(findPipelineKeyDropdown().exists()).toBe(false);
});
it('does not render tabs nor buttons', () => { it('does not render tabs nor buttons', () => {
expect(findNavigationTabs().exists()).toBe(false); expect(findNavigationTabs().exists()).toBe(false);
expect(findTab('all').exists()).toBe(false); expect(findTab('all').exists()).toBe(false);
......
...@@ -8,6 +8,7 @@ import PipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_tr ...@@ -8,6 +8,7 @@ import PipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_tr
import PipelineUrl from '~/pipelines/components/pipelines_list/pipeline_url.vue'; import PipelineUrl from '~/pipelines/components/pipelines_list/pipeline_url.vue';
import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue'; import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
import PipelinesTimeago from '~/pipelines/components/pipelines_list/time_ago.vue'; import PipelinesTimeago from '~/pipelines/components/pipelines_list/time_ago.vue';
import { PipelineKeyOptions } from '~/pipelines/constants';
import eventHub from '~/pipelines/event_hub'; import eventHub from '~/pipelines/event_hub';
import CiBadge from '~/vue_shared/components/ci_badge_link.vue'; import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
...@@ -24,6 +25,7 @@ describe('Pipelines Table', () => { ...@@ -24,6 +25,7 @@ describe('Pipelines Table', () => {
const defaultProps = { const defaultProps = {
pipelines: [], pipelines: [],
viewType: 'root', viewType: 'root',
pipelineKeyOption: PipelineKeyOptions[0],
}; };
const createMockPipeline = () => { const createMockPipeline = () => {
...@@ -80,7 +82,7 @@ describe('Pipelines Table', () => { ...@@ -80,7 +82,7 @@ describe('Pipelines Table', () => {
it('should render table head with correct columns', () => { it('should render table head with correct columns', () => {
expect(findStatusTh().text()).toBe('Status'); expect(findStatusTh().text()).toBe('Status');
expect(findPipelineTh().text()).toBe('Pipeline'); expect(findPipelineTh().text()).toBe('Pipeline ID');
expect(findTriggererTh().text()).toBe('Triggerer'); expect(findTriggererTh().text()).toBe('Triggerer');
expect(findCommitTh().text()).toBe('Commit'); expect(findCommitTh().text()).toBe('Commit');
expect(findStagesTh().text()).toBe('Stages'); expect(findStagesTh().text()).toBe('Stages');
......
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