Commit b28833cc authored by Frédéric Caplette's avatar Frédéric Caplette Committed by Nicolò Maria Mezzopera

Add tag to schedule pipeline

On the pipeline tab page,
if the pipeline was triggered by
a schedule, we add a tag that
indicates that origin. There is
also a tooltip on hover and
clicking on it links to the
schedule pipeline page.
parent 2507dfd2
...@@ -40,6 +40,7 @@ document.addEventListener( ...@@ -40,6 +40,7 @@ document.addEventListener(
props: { props: {
store: this.store, store: this.store,
endpoint: this.dataset.endpoint, endpoint: this.dataset.endpoint,
pipelineScheduleUrl: this.dataset.pipelineScheduleUrl,
helpPagePath: this.dataset.helpPagePath, helpPagePath: this.dataset.helpPagePath,
emptyStateSvgPath: this.dataset.emptyStateSvgPath, emptyStateSvgPath: this.dataset.emptyStateSvgPath,
errorStateSvgPath: this.dataset.errorStateSvgPath, errorStateSvgPath: this.dataset.errorStateSvgPath,
......
<script> <script>
import { GlLink, GlTooltipDirective } from '@gitlab/ui'; import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import { escape } from 'lodash'; import { escape } from 'lodash';
import { SCHEDULE_ORIGIN } from '../../constants';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import popover from '~/vue_shared/directives/popover'; import popover from '~/vue_shared/directives/popover';
...@@ -27,6 +28,10 @@ export default { ...@@ -27,6 +28,10 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
pipelineScheduleUrl: {
type: String,
required: true,
},
autoDevopsHelpPath: { autoDevopsHelpPath: {
type: String, type: String,
required: true, required: true,
...@@ -36,6 +41,9 @@ export default { ...@@ -36,6 +41,9 @@ export default {
user() { user() {
return this.pipeline.user; return this.pipeline.user;
}, },
isScheduled() {
return this.pipeline.source === SCHEDULE_ORIGIN;
},
popoverOptions() { popoverOptions() {
return { return {
html: true, html: true,
...@@ -61,16 +69,28 @@ export default { ...@@ -61,16 +69,28 @@ export default {
<gl-link <gl-link
:href="pipeline.path" :href="pipeline.path"
class="js-pipeline-url-link js-onboarding-pipeline-item" class="js-pipeline-url-link js-onboarding-pipeline-item"
data-testid="pipeline-url-link"
data-qa-selector="pipeline_url_link" data-qa-selector="pipeline_url_link"
> >
<span class="pipeline-id">#{{ pipeline.id }}</span> <span class="pipeline-id">#{{ pipeline.id }}</span>
</gl-link> </gl-link>
<div class="label-container"> <div class="label-container">
<gl-link v-if="isScheduled" :href="pipelineScheduleUrl" target="__blank">
<span
v-gl-tooltip
:title="__('This pipeline was triggered by a schedule.')"
class="badge badge-info"
data-testid="pipeline-url-scheduled"
>
{{ __('Scheduled') }}
</span>
</gl-link>
<span <span
v-if="pipeline.flags.latest" v-if="pipeline.flags.latest"
v-gl-tooltip v-gl-tooltip
:title="__('Latest pipeline for the most recent commit on this branch')" :title="__('Latest pipeline for the most recent commit on this branch')"
class="js-pipeline-url-latest badge badge-success" class="js-pipeline-url-latest badge badge-success"
data-testid="pipeline-url-latest"
> >
{{ __('latest') }} {{ __('latest') }}
</span> </span>
...@@ -79,6 +99,7 @@ export default { ...@@ -79,6 +99,7 @@ export default {
v-gl-tooltip v-gl-tooltip
:title="pipeline.yaml_errors" :title="pipeline.yaml_errors"
class="js-pipeline-url-yaml badge badge-danger" class="js-pipeline-url-yaml badge badge-danger"
data-testid="pipeline-url-yaml"
> >
{{ __('yaml invalid') }} {{ __('yaml invalid') }}
</span> </span>
...@@ -87,6 +108,7 @@ export default { ...@@ -87,6 +108,7 @@ export default {
v-gl-tooltip v-gl-tooltip
:title="pipeline.failure_reason" :title="pipeline.failure_reason"
class="js-pipeline-url-failure badge badge-danger" class="js-pipeline-url-failure badge badge-danger"
data-testid="pipeline-url-failure"
> >
{{ __('error') }} {{ __('error') }}
</span> </span>
...@@ -95,10 +117,15 @@ export default { ...@@ -95,10 +117,15 @@ export default {
v-popover="popoverOptions" v-popover="popoverOptions"
tabindex="0" tabindex="0"
class="js-pipeline-url-autodevops badge badge-info autodevops-badge" class="js-pipeline-url-autodevops badge badge-info autodevops-badge"
data-testid="pipeline-url-autodevops"
role="button" role="button"
>{{ __('Auto DevOps') }}</gl-link >{{ __('Auto DevOps') }}</gl-link
> >
<span v-if="pipeline.flags.stuck" class="js-pipeline-url-stuck badge badge-warning"> <span
v-if="pipeline.flags.stuck"
class="js-pipeline-url-stuck badge badge-warning"
data-testid="pipeline-url-stuck"
>
{{ __('stuck') }} {{ __('stuck') }}
</span> </span>
<span <span
...@@ -110,6 +137,7 @@ export default { ...@@ -110,6 +137,7 @@ export default {
) )
" "
class="js-pipeline-url-detached badge badge-info" class="js-pipeline-url-detached badge badge-info"
data-testid="pipeline-url-detached"
> >
{{ __('detached') }} {{ __('detached') }}
</span> </span>
......
...@@ -42,6 +42,11 @@ export default { ...@@ -42,6 +42,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
pipelineScheduleUrl: {
type: String,
required: false,
default: '',
},
helpPagePath: { helpPagePath: {
type: String, type: String,
required: true, required: true,
...@@ -340,6 +345,7 @@ export default { ...@@ -340,6 +345,7 @@ export default {
<div v-else-if="stateToRender === $options.stateMap.tableList" class="table-holder"> <div v-else-if="stateToRender === $options.stateMap.tableList" class="table-holder">
<pipelines-table-component <pipelines-table-component
:pipelines="state.pipelines" :pipelines="state.pipelines"
:pipeline-schedule-url="pipelineScheduleUrl"
:update-graph-dropdown="updateGraphDropdown" :update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsPath" :auto-devops-help-path="autoDevopsPath"
:view-type="viewType" :view-type="viewType"
......
...@@ -22,6 +22,11 @@ export default { ...@@ -22,6 +22,11 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
pipelineScheduleUrl: {
type: String,
required: false,
default: '',
},
updateGraphDropdown: { updateGraphDropdown: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -91,6 +96,7 @@ export default { ...@@ -91,6 +96,7 @@ export default {
v-for="model in pipelines" v-for="model in pipelines"
:key="model.id" :key="model.id"
:pipeline="model" :pipeline="model"
:pipeline-schedule-url="pipelineScheduleUrl"
:update-graph-dropdown="updateGraphDropdown" :update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsHelpPath" :auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType" :view-type="viewType"
......
...@@ -35,6 +35,11 @@ export default { ...@@ -35,6 +35,11 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
pipelineScheduleUrl: {
type: String,
required: false,
default: '',
},
updateGraphDropdown: { updateGraphDropdown: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -274,7 +279,11 @@ export default { ...@@ -274,7 +279,11 @@ export default {
</div> </div>
</div> </div>
<pipeline-url :pipeline="pipeline" :auto-devops-help-path="autoDevopsHelpPath" /> <pipeline-url
:pipeline="pipeline"
:pipeline-schedule-url="pipelineScheduleUrl"
:auto-devops-help-path="autoDevopsHelpPath"
/>
<pipeline-triggerer :pipeline="pipeline" /> <pipeline-triggerer :pipeline="pipeline" />
<div class="table-section section-wrap section-20"> <div class="table-section section-wrap section-20">
......
...@@ -7,6 +7,7 @@ export const FILTER_PIPELINES_SEARCH_DELAY = 200; ...@@ -7,6 +7,7 @@ export const FILTER_PIPELINES_SEARCH_DELAY = 200;
export const ANY_TRIGGER_AUTHOR = 'Any'; export const ANY_TRIGGER_AUTHOR = 'Any';
export const SUPPORTED_FILTER_PARAMETERS = ['username', 'ref', 'status']; export const SUPPORTED_FILTER_PARAMETERS = ['username', 'ref', 'status'];
export const FILTER_TAG_IDENTIFIER = 'tag'; export const FILTER_TAG_IDENTIFIER = 'tag';
export const SCHEDULE_ORIGIN = 'schedule';
export const TestStatus = { export const TestStatus = {
FAILED: 'failed', FAILED: 'failed',
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
params: params.to_json, params: params.to_json,
"help-page-path" => help_page_path('ci/quick_start/README'), "help-page-path" => help_page_path('ci/quick_start/README'),
"help-auto-devops-path" => help_page_path('topics/autodevops/index.md'), "help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
"pipeline-schedule-url" => pipeline_schedules_path(@project),
"empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'), "empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'),
"error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'), "error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'),
"no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'), "no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'),
......
---
title: Provide a label for 'Scheduled Pipeline' in the pipelines overview page
merge_request: 35554
author:
type: added
...@@ -23587,6 +23587,9 @@ msgstr "" ...@@ -23587,6 +23587,9 @@ msgstr ""
msgid "This pipeline was triggered by a parent pipeline" msgid "This pipeline was triggered by a parent pipeline"
msgstr "" msgstr ""
msgid "This pipeline was triggered by a schedule."
msgstr ""
msgid "This project" msgid "This project"
msgstr "" msgstr ""
......
...@@ -8,101 +8,133 @@ $.fn.popover = () => {}; ...@@ -8,101 +8,133 @@ $.fn.popover = () => {};
describe('Pipeline Url Component', () => { describe('Pipeline Url Component', () => {
let wrapper; let wrapper;
const findPipelineUrlLink = () => wrapper.find('[data-testid="pipeline-url-link"]');
const findScheduledTag = () => wrapper.find('[data-testid="pipeline-url-scheduled"]');
const findLatestTag = () => wrapper.find('[data-testid="pipeline-url-latest"]');
const findYamlTag = () => wrapper.find('[data-testid="pipeline-url-yaml"]');
const findFailureTag = () => wrapper.find('[data-testid="pipeline-url-failure"]');
const findAutoDevopsTag = () => wrapper.find('[data-testid="pipeline-url-autodevops"]');
const findStuckTag = () => wrapper.find('[data-testid="pipeline-url-stuck"]');
const findDetachedTag = () => wrapper.find('[data-testid="pipeline-url-detached"]');
const defaultProps = {
pipeline: {
id: 1,
path: 'foo',
flags: {},
},
autoDevopsHelpPath: 'foo',
pipelineScheduleUrl: 'foo',
};
const createComponent = props => { const createComponent = props => {
wrapper = shallowMount(PipelineUrlComponent, { wrapper = shallowMount(PipelineUrlComponent, {
propsData: props, propsData: { ...defaultProps, ...props },
}); });
}; };
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
it('should render a table cell', () => { it('should render a table cell', () => {
createComponent();
expect(wrapper.attributes('class')).toContain('table-section');
});
it('should render a link the provided path and id', () => {
createComponent();
expect(findPipelineUrlLink().attributes('href')).toBe('foo');
expect(findPipelineUrlLink().text()).toBe('#1');
});
it('should render the stuck tag when flag is provided', () => {
createComponent({ createComponent({
pipeline: { pipeline: {
id: 1, flags: {
path: 'foo', stuck: true,
flags: {}, },
}, },
autoDevopsHelpPath: 'foo',
}); });
expect(wrapper.attributes('class')).toContain('table-section'); expect(findStuckTag().text()).toContain('stuck');
}); });
it('should render a link the provided path and id', () => { it('should render latest tag when flag is provided', () => {
createComponent({ createComponent({
pipeline: { pipeline: {
id: 1, flags: {
path: 'foo', latest: true,
flags: {}, },
}, },
autoDevopsHelpPath: 'foo',
}); });
expect(wrapper.find('.js-pipeline-url-link').attributes('href')).toBe('foo'); expect(findLatestTag().text()).toContain('latest');
expect(wrapper.find('.js-pipeline-url-link span').text()).toBe('#1');
}); });
it('should render latest, yaml invalid, merge request, and stuck flags when provided', () => { it('should render a yaml badge when it is invalid', () => {
createComponent({ createComponent({
pipeline: { pipeline: {
id: 1,
path: 'foo',
flags: { flags: {
latest: true,
yaml_errors: true, yaml_errors: true,
stuck: true,
merge_request_pipeline: true,
detached_merge_request_pipeline: true,
}, },
}, },
autoDevopsHelpPath: 'foo',
}); });
expect(wrapper.find('.js-pipeline-url-latest').text()).toContain('latest'); expect(findYamlTag().text()).toContain('yaml invalid');
});
expect(wrapper.find('.js-pipeline-url-yaml').text()).toContain('yaml invalid');
expect(wrapper.find('.js-pipeline-url-stuck').text()).toContain('stuck'); it('should render an autodevops badge when flag is provided', () => {
createComponent({
pipeline: {
flags: {
auto_devops: true,
},
},
});
expect(wrapper.find('.js-pipeline-url-detached').text()).toContain('detached'); expect(trimText(findAutoDevopsTag().text())).toBe('Auto DevOps');
}); });
it('should render a badge for autodevops', () => { it('should render a detached badge when flag is provided', () => {
createComponent({ createComponent({
pipeline: { pipeline: {
id: 1,
path: 'foo',
flags: { flags: {
latest: true, detached_merge_request_pipeline: true,
yaml_errors: true,
stuck: true,
auto_devops: true,
}, },
}, },
autoDevopsHelpPath: 'foo',
}); });
expect(trimText(wrapper.find('.js-pipeline-url-autodevops').text())).toEqual('Auto DevOps'); expect(findDetachedTag().text()).toContain('detached');
}); });
it('should render error badge when pipeline has a failure reason set', () => { it('should render error badge when pipeline has a failure reason set', () => {
createComponent({ createComponent({
pipeline: { pipeline: {
id: 1,
path: 'foo',
flags: { flags: {
failure_reason: true, failure_reason: true,
}, },
failure_reason: 'some reason', failure_reason: 'some reason',
}, },
autoDevopsHelpPath: 'foo',
}); });
expect(wrapper.find('.js-pipeline-url-failure').text()).toContain('error'); expect(findFailureTag().text()).toContain('error');
expect(wrapper.find('.js-pipeline-url-failure').attributes('title')).toContain('some reason'); expect(findFailureTag().attributes('title')).toContain('some reason');
});
it('should render scheduled badge when pipeline was triggered by a schedule', () => {
createComponent({
pipeline: {
flags: {},
source: 'schedule',
},
});
expect(findScheduledTag().exists()).toBe(true);
expect(findScheduledTag().text()).toContain('Scheduled');
}); });
}); });
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