Commit 0052c811 authored by Jose Vargas's avatar Jose Vargas

Add pipeline iid dropdown

This adds a pipeline key selection dropdown that
allows to change the pipeline column to display
either the Pipeline ID or the Pipeline IID in
the pipelines table

Changelog: added
parent 8b030d4c
......@@ -2,6 +2,7 @@
import { GlButton, GlEmptyState, GlLoadingIcon, GlModal, GlLink } from '@gitlab/ui';
import { getParameterByName } from '~/lib/utils/url_utility';
import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
import { PipelineKeyOptions } from '~/pipelines/constants';
import eventHub from '~/pipelines/event_hub';
import PipelinesMixin from '~/pipelines/mixins/pipelines_mixin';
import PipelinesService from '~/pipelines/services/pipelines_service';
......@@ -10,6 +11,7 @@ import TablePagination from '~/vue_shared/components/pagination/table_pagination
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
PipelineKeyOptions,
components: {
GlButton,
GlEmptyState,
......@@ -205,6 +207,7 @@ export default {
:pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown"
:view-type="viewType"
:pipeline-key-option="$options.PipelineKeyOptions[0]"
>
<template #table-header-actions>
<div v-if="canRenderPipelineButton" class="gl-text-right">
......
......@@ -29,6 +29,10 @@ export default {
type: String,
required: true,
},
pipelineKey: {
type: String,
required: true,
},
},
computed: {
user() {
......@@ -60,7 +64,7 @@ export default {
data-testid="pipeline-url-link"
data-qa-selector="pipeline_url_link"
>
#{{ pipeline.id }}
#{{ pipeline[pipelineKey] }}
</gl-link>
<div class="label-container">
<gl-badge
......
<script>
import { GlEmptyState, GlIcon, GlLoadingIcon } from '@gitlab/ui';
import { GlDropdown, GlDropdownItem, GlEmptyState, GlIcon, GlLoadingIcon } from '@gitlab/ui';
import { isEqual } from 'lodash';
import createFlash from '~/flash';
import { getParameterByName } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.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 PipelinesService from '../../services/pipelines_service';
import { validateParams } from '../../utils';
......@@ -16,8 +21,11 @@ import PipelinesFilteredSearch from './pipelines_filtered_search.vue';
import PipelinesTableComponent from './pipelines_table.vue';
export default {
PipelineKeyOptions,
components: {
EmptyState,
GlDropdown,
GlDropdownItem,
GlEmptyState,
GlIcon,
GlLoadingIcon,
......@@ -114,6 +122,7 @@ export default {
page: getParameterByName('page') || '1',
requestData: {},
isResetCacheButtonLoading: false,
selectedPipelineKeyOption: this.$options.PipelineKeyOptions[0],
};
},
stateMap: {
......@@ -301,6 +310,9 @@ export default {
this.updateContent(this.requestData);
},
changeVisibilityPipelineID(val) {
this.selectedPipelineKeyOption = val;
},
},
};
</script>
......@@ -330,12 +342,31 @@ export default {
/>
</div>
<pipelines-filtered-search
v-if="stateToRender !== $options.stateMap.emptyState"
:project-id="projectId"
:params="validatedParams"
@filterPipelines="filterPipelines"
/>
<div v-if="stateToRender !== $options.stateMap.emptyState" class="gl-display-flex">
<div class="row-content-block gl-display-flex gl-flex-grow-1">
<pipelines-filtered-search
class="gl-display-flex gl-flex-grow-1 gl-mr-4"
: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">
<gl-loading-icon
......@@ -374,6 +405,7 @@ export default {
:pipeline-schedule-url="pipelineScheduleUrl"
:update-graph-dropdown="updateGraphDropdown"
:view-type="viewType"
:pipeline-key-option="selectedPipelineKeyOption"
/>
</div>
......
......@@ -101,12 +101,10 @@ export default {
</script>
<template>
<div class="row-content-block">
<gl-filtered-search
v-model="value"
:placeholder="__('Filter pipelines')"
:available-tokens="tokens"
@submit="onSubmit"
/>
</div>
<gl-filtered-search
v-model="value"
:placeholder="__('Filter pipelines')"
:available-tokens="tokens"
@submit="onSubmit"
/>
</template>
......@@ -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!';
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: {
GlTable,
LinkedPipelinesMiniList: () =>
......@@ -109,6 +52,10 @@ export default {
type: String,
required: true,
},
pipelineKeyOption: {
type: Object,
required: true,
},
},
data() {
return {
......@@ -118,6 +65,68 @@ export default {
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: {
pipelines() {
this.cancelingPipeline = null;
......@@ -148,7 +157,7 @@ export default {
<template>
<div class="ci-table">
<gl-table
:fields="$options.fields"
:fields="tableFields"
:items="pipelines"
tbody-tr-class="commit"
:tbody-tr-attr="{ 'data-testid': 'pipeline-table-row' }"
......@@ -169,7 +178,11 @@ export default {
</template>
<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 #cell(triggerer)="{ item }">
......
......@@ -35,3 +35,17 @@ export const POST_FAILURE = 'post_failure';
export const UNSUPPORTED_DATA = 'unsupported_data';
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:
- 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))
[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
Pipelines can be manually executed, with predefined or manually-specified [variables](../variables/index.md).
......
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
import { PipelineKeyOptions } from '~/pipelines/constants';
import { triggeredBy, triggered } from './mock_data';
......@@ -15,6 +16,7 @@ describe('Pipelines Table', () => {
const defaultProps = {
pipelines: [],
viewType: 'root',
pipelineKeyOption: PipelineKeyOptions[0],
};
const createMockPipeline = () => {
......
......@@ -24153,6 +24153,12 @@ msgstr ""
msgid "Pipeline %{label} for \"%{dataTitle}\""
msgstr ""
msgid "Pipeline ID"
msgstr ""
msgid "Pipeline IID"
msgstr ""
msgid "Pipeline Schedule"
msgstr ""
......@@ -30465,6 +30471,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."
msgstr ""
msgid "Show Pipeline ID"
msgstr ""
msgid "Show Pipeline IID"
msgstr ""
msgid "Show all activity"
msgstr ""
......
......@@ -585,6 +585,26 @@ RSpec.describe 'Pipelines', :js do
expect(page).to have_selector('.gl-pagination .page-link', count: 4)
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
describe 'GET /:project/-/pipelines/show' do
......
......@@ -28,6 +28,7 @@ describe('Pipeline Url Component', () => {
flags: {},
},
pipelineScheduleUrl: 'foo',
pipelineKey: 'id',
};
const createComponent = (props) => {
......
......@@ -74,6 +74,7 @@ describe('Pipelines', () => {
const findTablePagination = () => wrapper.findComponent(TablePagination);
const findTab = (tab) => wrapper.findByTestId(`pipelines-tab-${tab}`);
const findPipelineKeyDropdown = () => wrapper.findByTestId('pipeline-key-dropdown');
const findRunPipelineButton = () => wrapper.findByTestId('run-pipeline-button');
const findCiLintButton = () => wrapper.findByTestId('ci-lint-button');
const findCleanCacheButton = () => wrapper.findByTestId('clear-cache-button');
......@@ -528,6 +529,10 @@ describe('Pipelines', () => {
expect(findFilteredSearch().exists()).toBe(true);
});
it('renders the pipeline key dropdown', () => {
expect(findPipelineKeyDropdown().exists()).toBe(true);
});
it('renders tab empty state finished scope', async () => {
mock.onGet(mockPipelinesEndpoint, { params: { scope: 'finished', page: '1' } }).reply(200, {
pipelines: [],
......@@ -623,6 +628,10 @@ describe('Pipelines', () => {
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', () => {
expect(findNavigationTabs().exists()).toBe(false);
expect(findTab('all').exists()).toBe(false);
......
......@@ -8,6 +8,7 @@ import PipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_tr
import PipelineUrl from '~/pipelines/components/pipelines_list/pipeline_url.vue';
import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
import PipelinesTimeago from '~/pipelines/components/pipelines_list/time_ago.vue';
import { PipelineKeyOptions } from '~/pipelines/constants';
import eventHub from '~/pipelines/event_hub';
import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
......@@ -24,6 +25,7 @@ describe('Pipelines Table', () => {
const defaultProps = {
pipelines: [],
viewType: 'root',
pipelineKeyOption: PipelineKeyOptions[0],
};
const createMockPipeline = () => {
......@@ -80,7 +82,7 @@ describe('Pipelines Table', () => {
it('should render table head with correct columns', () => {
expect(findStatusTh().text()).toBe('Status');
expect(findPipelineTh().text()).toBe('Pipeline');
expect(findPipelineTh().text()).toBe('Pipeline ID');
expect(findTriggererTh().text()).toBe('Triggerer');
expect(findCommitTh().text()).toBe('Commit');
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