summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/pipelines
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/pipelines')
-rw-r--r--app/assets/javascripts/pipelines/components/graph/constants.js4
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue21
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue17
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_item.vue108
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue7
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue7
-rw-r--r--app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue26
-rw-r--r--app/assets/javascripts/pipelines/components/parsing_utils.js10
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_mini_graph/job_item.vue15
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue7
-rw-r--r--app/assets/javascripts/pipelines/constants.js1
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js7
-rw-r--r--app/assets/javascripts/pipelines/pipeline_tabs.js2
15 files changed, 222 insertions, 16 deletions
diff --git a/app/assets/javascripts/pipelines/components/graph/constants.js b/app/assets/javascripts/pipelines/components/graph/constants.js
index 85ca52f633e..e650a48bc2a 100644
--- a/app/assets/javascripts/pipelines/components/graph/constants.js
+++ b/app/assets/javascripts/pipelines/components/graph/constants.js
@@ -10,6 +10,8 @@ export const ONE_COL_WIDTH = 180;
export const STAGE_VIEW = 'stage';
export const LAYER_VIEW = 'layer';
+
+export const SKIP_RETRY_MODAL_KEY = 'skip_retry_modal';
export const VIEW_TYPE_KEY = 'pipeline_graph_view_type';
export const SINGLE_JOB = 'single_job';
@@ -20,3 +22,5 @@ export const BRIDGE_KIND = 'BRIDGE';
export const ACTION_FAILURE = 'action_failure';
export const IID_FAILURE = 'missing_iid';
+
+export const RETRY_ACTION_TITLE = 'Retry';
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 1a05710a13e..49df71beeec 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -2,7 +2,10 @@
import { reportToSentry } from '../../utils';
import LinkedGraphWrapper from '../graph_shared/linked_graph_wrapper.vue';
import LinksLayer from '../graph_shared/links_layer.vue';
-import { generateColumnsFromLayersListMemoized } from '../parsing_utils';
+import {
+ generateColumnsFromLayersListMemoized,
+ keepLatestDownstreamPipelines,
+} from '../parsing_utils';
import { DOWNSTREAM, MAIN, UPSTREAM, ONE_COL_WIDTH, STAGE_VIEW } from './constants';
import LinkedPipelinesColumn from './linked_pipelines_column.vue';
import StageColumnComponent from './stage_column_component.vue';
@@ -44,6 +47,11 @@ export default {
required: false,
default: () => ({}),
},
+ skipRetryModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
type: {
type: String,
required: false,
@@ -76,7 +84,9 @@ export default {
return `${this.$options.BASE_CONTAINER_ID}-${this.pipeline.id}`;
},
downstreamPipelines() {
- return this.hasDownstreamPipelines ? this.pipeline.downstream : [];
+ return this.hasDownstreamPipelines
+ ? keepLatestDownstreamPipelines(this.pipeline.downstream)
+ : [];
},
layout() {
return this.isStageView
@@ -181,9 +191,11 @@ export default {
:linked-pipelines="upstreamPipelines"
:column-title="__('Upstream')"
:show-links="showJobLinks"
+ :skip-retry-modal="skipRetryModal"
:type="$options.pipelineTypeConstants.UPSTREAM"
:view-type="viewType"
@error="onError"
+ @setSkipRetryModal="$emit('setSkipRetryModal')"
/>
</template>
<template #main>
@@ -210,11 +222,13 @@ export default {
:highlighted-jobs="highlightedJobs"
:is-stage-view="isStageView"
:job-hovered="hoveredJobName"
+ :skip-retry-modal="skipRetryModal"
:source-job-hovered="hoveredSourceJobName"
:pipeline-expanded="pipelineExpanded"
:pipeline-id="pipeline.id"
:user-permissions="pipeline.userPermissions"
@refreshPipelineGraph="$emit('refreshPipelineGraph')"
+ @setSkipRetryModal="$emit('setSkipRetryModal')"
@jobHover="setJob"
@updateMeasurements="getMeasurements"
/>
@@ -228,12 +242,15 @@ export default {
:config-paths="configPaths"
:linked-pipelines="downstreamPipelines"
:column-title="__('Downstream')"
+ :skip-retry-modal="skipRetryModal"
:show-links="showJobLinks"
:type="$options.pipelineTypeConstants.DOWNSTREAM"
:view-type="viewType"
+ data-testid="downstream-pipelines"
@downstreamHovered="setSourceJob"
@pipelineExpandToggle="togglePipelineExpanded"
@refreshPipelineGraph="$emit('refreshPipelineGraph')"
+ @setSkipRetryModal="$emit('setSkipRetryModal')"
@scrollContainer="slidePipelineContainer"
@error="onError"
/>
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
index 4d7596e6e16..8f76d7535f1 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
@@ -8,7 +8,14 @@ import { DEFAULT, DRAW_FAILURE, LOAD_FAILURE } from '../../constants';
import DismissPipelineGraphCallout from '../../graphql/mutations/dismiss_pipeline_notification.graphql';
import getPipelineQuery from '../../graphql/queries/get_pipeline_header_data.query.graphql';
import { reportToSentry, reportMessageToSentry } from '../../utils';
-import { ACTION_FAILURE, IID_FAILURE, LAYER_VIEW, STAGE_VIEW, VIEW_TYPE_KEY } from './constants';
+import {
+ ACTION_FAILURE,
+ IID_FAILURE,
+ LAYER_VIEW,
+ SKIP_RETRY_MODAL_KEY,
+ STAGE_VIEW,
+ VIEW_TYPE_KEY,
+} from './constants';
import PipelineGraph from './graph_component.vue';
import GraphViewSelector from './graph_view_selector.vue';
import {
@@ -53,6 +60,7 @@ export default {
currentViewType: STAGE_VIEW,
canRefetchHeaderPipeline: false,
pipeline: null,
+ skipRetryModal: false,
showAlert: false,
showLinks: false,
};
@@ -206,8 +214,8 @@ export default {
if (!this.pipelineIid) {
this.reportFailure({ type: IID_FAILURE, skipSentry: true });
}
-
toggleQueryPollingByVisibility(this.$apollo.queries.pipeline);
+ this.skipRetryModal = Boolean(JSON.parse(localStorage.getItem(SKIP_RETRY_MODAL_KEY)));
},
errorCaptured(err, _vm, info) {
reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
@@ -259,6 +267,9 @@ export default {
updateShowLinksState(val) {
this.showLinks = val;
},
+ setSkipRetryModal() {
+ this.skipRetryModal = true;
+ },
updateViewType(type) {
this.currentViewType = type;
},
@@ -293,10 +304,12 @@ export default {
:config-paths="configPaths"
:pipeline="pipeline"
:computed-pipeline-info="getPipelineInfo()"
+ :skip-retry-modal="skipRetryModal"
:show-links="showLinks"
:view-type="graphViewType"
@error="reportFailure"
@refreshPipelineGraph="refreshPipelineGraph"
+ @setSkipRetryModal="setSkipRetryModal"
/>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue
index 4f2be27486c..992e3d2f552 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_item.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue
@@ -1,13 +1,14 @@
<script>
-import { GlBadge, GlLink, GlTooltipDirective } from '@gitlab/ui';
+import { GlBadge, GlForm, GlFormCheckbox, GlLink, GlModal, GlTooltipDirective } from '@gitlab/ui';
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
+import { helpPagePath } from '~/helpers/help_page_helper';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
-import { sprintf, __ } from '~/locale';
+import { __, s__, sprintf } from '~/locale';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import { reportToSentry } from '../../utils';
import ActionComponent from '../jobs_shared/action_component.vue';
import JobNameComponent from '../jobs_shared/job_name_component.vue';
-import { BRIDGE_KIND, SINGLE_JOB } from './constants';
+import { BRIDGE_KIND, RETRY_ACTION_TITLE, SINGLE_JOB, SKIP_RETRY_MODAL_KEY } from './constants';
/**
* Renders the badge for the pipeline graph and the job's dropdown.
@@ -35,17 +36,32 @@ import { BRIDGE_KIND, SINGLE_JOB } from './constants';
*/
export default {
+ confirmationModalDocLink: helpPagePath('/ci/pipelines/downstream_pipelines'),
i18n: {
bridgeBadgeText: __('Trigger job'),
unauthorizedTooltip: __('You are not authorized to run this manual job'),
+ confirmationModal: {
+ title: s__('PipelineGraph|Are you sure you want to retry %{jobName}?'),
+ description: s__(
+ 'PipelineGraph|Retrying a trigger job will create a new downstream pipeline.',
+ ),
+ linkText: s__('PipelineGraph|What is a downstream pipeline?'),
+ footer: __("Don't show this again"),
+ actionPrimary: { text: __('Retry') },
+ actionCancel: { text: __('Cancel') },
+ },
+ runAgainTooltipText: __('Run again'),
},
hoverClass: 'gl-shadow-x0-y0-b3-s1-blue-500',
components: {
ActionComponent,
CiIcon,
- JobNameComponent,
GlBadge,
+ GlForm,
+ GlFormCheckbox,
GlLink,
+ GlModal,
+ JobNameComponent,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -86,6 +102,11 @@ export default {
required: false,
default: -1,
},
+ skipRetryModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
sourceJobHovered: {
type: String,
required: false,
@@ -102,6 +123,13 @@ export default {
default: SINGLE_JOB,
},
},
+ data() {
+ return {
+ currentSkipModalValue: this.skipRetryModal,
+ showConfirmationModal: false,
+ shouldTriggerActionClick: false,
+ };
+ },
computed: {
boundary() {
return this.dropdownLength === 1 ? 'viewport' : 'scrollParent';
@@ -115,6 +143,12 @@ export default {
hasDetails() {
return this.status.hasDetails;
},
+ hasRetryAction() {
+ return Boolean(this.job?.status?.action?.title === RETRY_ACTION_TITLE);
+ },
+ isRetryableBridge() {
+ return this.isBridge && this.hasRetryAction;
+ },
isSingleItem() {
return this.type === SINGLE_JOB;
},
@@ -127,6 +161,11 @@ export default {
nameComponent() {
return this.hasDetails ? 'gl-link' : 'div';
},
+ retryTriggerJobWarningText() {
+ return sprintf(this.$options.i18n.confirmationModal.title, {
+ jobName: this.job.name,
+ });
+ },
showStageName() {
return Boolean(this.stageName);
},
@@ -205,11 +244,34 @@ export default {
},
];
},
+ withConfirmationModal() {
+ return this.isRetryableBridge && !this.skipRetryModal;
+ },
+ jobActionTooltipText() {
+ const { group } = this.status;
+ const { title, icon } = this.status.action;
+
+ return icon === 'retry' && group === 'success'
+ ? this.$options.i18n.runAgainTooltipText
+ : title;
+ },
+ },
+ watch: {
+ skipRetryModal(val) {
+ this.currentSkipModalValue = val;
+ this.shouldTriggerActionClick = false;
+ },
},
errorCaptured(err, _vm, info) {
reportToSentry('job_item', `error: ${err}, info: ${info}`);
},
methods: {
+ handleConfirmationModalPreferences() {
+ if (this.currentSkipModalValue) {
+ this.$emit('setSkipRetryModal');
+ localStorage.setItem(SKIP_RETRY_MODAL_KEY, String(this.currentSkipModalValue));
+ }
+ },
hideTooltips() {
this.$root.$emit(BV_HIDE_TOOLTIP);
},
@@ -227,6 +289,15 @@ export default {
pipelineActionRequestComplete() {
this.$emit('pipelineActionRequestComplete');
},
+ executePendingAction() {
+ this.shouldTriggerActionClick = true;
+ },
+ showActionConfirmationModal() {
+ this.showConfirmationModal = true;
+ },
+ toggleSkipRetryModalCheckbox() {
+ this.currentSkipModalValue = !this.currentSkipModalValue;
+ },
},
};
</script>
@@ -272,12 +343,16 @@ export default {
<action-component
v-if="hasAction"
- :tooltip-text="status.action.title"
+ :tooltip-text="jobActionTooltipText"
:link="status.action.path"
:action-icon="status.action.icon"
class="gl-mr-1"
+ :should-trigger-click="shouldTriggerActionClick"
+ :with-confirmation-modal="withConfirmationModal"
data-qa-selector="job_action_button"
+ @actionButtonClicked="handleConfirmationModalPreferences"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
+ @showActionConfirmationModal="showActionConfirmationModal"
/>
<action-component
v-if="hasUnauthorizedManualAction"
@@ -287,5 +362,28 @@ export default {
:link="`unauthorized-${computedJobId}`"
class="gl-mr-1"
/>
+ <gl-modal
+ v-if="showConfirmationModal"
+ ref="modal"
+ v-model="showConfirmationModal"
+ modal-id="action-confirmation-modal"
+ :title="retryTriggerJobWarningText"
+ :action-cancel="$options.i18n.confirmationModal.actionCancel"
+ :action-primary="$options.i18n.confirmationModal.actionPrimary"
+ @primary="executePendingAction"
+ @close="handleConfirmationModalPreferences"
+ @hide="handleConfirmationModalPreferences"
+ >
+ <p class="gl-mb-1">{{ $options.i18n.confirmationModal.description }}</p>
+ <gl-link :href="$options.confirmationModalDocLink" target="_blank">{{
+ $options.i18n.confirmationModal.linkText
+ }}</gl-link>
+ <div class="gl-mt-4 gl-display-flex">
+ <gl-form>
+ <gl-form-checkbox class="gl-min-h-0" @input="toggleSkipRetryModalCheckbox" />
+ </gl-form>
+ <p class="gl-m-0">{{ $options.i18n.confirmationModal.footer }}</p>
+ </div>
+ </gl-modal>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
index 225706265c3..9b4e5d471d6 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
@@ -7,13 +7,13 @@ import {
GlTooltip,
GlTooltipDirective,
} from '@gitlab/ui';
+import { TYPENAME_CI_PIPELINE } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { __, sprintf } from '~/locale';
import CancelPipelineMutation from '~/pipelines/graphql/mutations/cancel_pipeline.mutation.graphql';
import RetryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline.mutation.graphql';
import CiStatus from '~/vue_shared/components/ci_icon.vue';
-import { PIPELINE_GRAPHQL_TYPE } from '../../constants';
import { reportToSentry } from '../../utils';
import { ACTION_FAILURE, DOWNSTREAM, UPSTREAM } from './constants';
@@ -118,7 +118,7 @@ export default {
return this.isUpstream ? 'gl-flex-direction-row-reverse' : 'gl-flex-direction-row';
},
graphqlPipelineId() {
- return convertToGraphQLId(PIPELINE_GRAPHQL_TYPE, this.pipeline.id);
+ return convertToGraphQLId(TYPENAME_CI_PIPELINE, this.pipeline.id);
},
hasUpdatePipelinePermissions() {
return Boolean(this.pipeline?.userPermissions?.updatePipeline);
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
index b06c2f15042..02e426064c9 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
@@ -36,6 +36,11 @@ export default {
type: Boolean,
required: true,
},
+ skipRetryModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
type: {
type: String,
required: true,
@@ -229,8 +234,10 @@ export default {
:pipeline="currentPipeline"
:computed-pipeline-info="getPipelineLayers(pipeline.id)"
:show-links="showLinks"
+ :skip-retry-modal="skipRetryModal"
:is-linked-pipeline="true"
:view-type="graphViewType"
+ @setSkipRetryModal="$emit('setSkipRetryModal')"
/>
</div>
</li>
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index 4aec28295bd..ffd0fec2ca8 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -53,6 +53,11 @@ export default {
required: false,
default: () => ({}),
},
+ skipRetryModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
sourceJobHovered: {
type: String,
required: false,
@@ -164,6 +169,7 @@ export default {
v-if="singleJobExists(group)"
:job="group.jobs[0]"
:job-hovered="jobHovered"
+ :skip-retry-modal="skipRetryModal"
:source-job-hovered="sourceJobHovered"
:pipeline-expanded="pipelineExpanded"
:pipeline-id="pipelineId"
@@ -174,6 +180,7 @@ export default {
'gl-transition-duration-slow gl-transition-timing-function-ease',
]"
@pipelineActionRequestComplete="$emit('refreshPipelineGraph')"
+ @setSkipRetryModal="$emit('setSkipRetryModal')"
/>
<div v-else-if="isParallel(group)" :class="{ 'gl-opacity-3': isFadedOut(group.name) }">
<job-group-dropdown
diff --git a/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue b/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue
index 387b01aee7e..7020bfc1e65 100644
--- a/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue
+++ b/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue
@@ -39,6 +39,16 @@ export default {
type: String,
required: true,
},
+ withConfirmationModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ shouldTriggerClick: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -52,6 +62,14 @@ export default {
return `${actionIconDash} js-icon-${actionIconDash}`;
},
},
+ watch: {
+ shouldTriggerClick(flag) {
+ if (flag && this.withConfirmationModal) {
+ this.executeAction();
+ this.$emit('actionButtonClicked');
+ }
+ },
+ },
errorCaptured(err, _vm, info) {
reportToSentry('action_component', `error: ${err}, info: ${info}`);
},
@@ -63,6 +81,13 @@ export default {
*
*/
onClickAction() {
+ if (this.withConfirmationModal) {
+ this.$emit('showActionConfirmationModal');
+ } else {
+ this.executeAction();
+ }
+ },
+ executeAction() {
this.$root.$emit(BV_HIDE_TOOLTIP, `js-ci-action-${this.link}`);
this.isDisabled = true;
this.isLoading = true;
@@ -91,6 +116,7 @@ export default {
<template>
<gl-button
:id="`js-ci-action-${link}`"
+ ref="button"
:class="cssClass"
:disabled="isDisabled"
class="js-ci-action gl-ci-action-icon-container ci-action-icon-container ci-action-icon-wrapper gl-display-flex gl-align-items-center gl-justify-content-center"
diff --git a/app/assets/javascripts/pipelines/components/parsing_utils.js b/app/assets/javascripts/pipelines/components/parsing_utils.js
index cae4e11c13f..e158f8809b5 100644
--- a/app/assets/javascripts/pipelines/components/parsing_utils.js
+++ b/app/assets/javascripts/pipelines/components/parsing_utils.js
@@ -170,3 +170,13 @@ export const generateColumnsFromLayersListBare = ({ stages, stagesLookup }, pipe
};
export const generateColumnsFromLayersListMemoized = memoize(generateColumnsFromLayersListBare);
+
+export const keepLatestDownstreamPipelines = (downstreamPipelines = []) => {
+ return downstreamPipelines.filter((pipeline) => {
+ if (pipeline.source_job) {
+ return !pipeline?.source_job?.retried || false;
+ }
+
+ return !pipeline?.sourceJob?.retried || false;
+ });
+};
diff --git a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/job_item.vue b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/job_item.vue
index 51b46f25048..66bf5068149 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/job_item.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/job_item.vue
@@ -2,7 +2,7 @@
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
-import { sprintf } from '~/locale';
+import { __, sprintf } from '~/locale';
import { reportToSentry } from '../../utils';
import ActionComponent from '../jobs_shared/action_component.vue';
import JobNameComponent from '../jobs_shared/job_name_component.vue';
@@ -33,6 +33,9 @@ import JobNameComponent from '../jobs_shared/job_name_component.vue';
*/
export default {
+ i18n: {
+ runAgainTooltipText: __('Run again'),
+ },
hoverClass: 'gl-shadow-x0-y0-b3-s1-blue-500',
components: {
ActionComponent,
@@ -129,6 +132,14 @@ export default {
? `${this.$options.hoverClass} ${this.cssClassJobName}`
: this.cssClassJobName;
},
+ jobActionTooltipText() {
+ const { group } = this.status;
+ const { title, icon } = this.status.action;
+
+ return icon === 'retry' && group === 'success'
+ ? this.$options.i18n.runAgainTooltipText
+ : title;
+ },
},
errorCaptured(err, _vm, info) {
reportToSentry('pipelines_job_item', `pipelines_job_item error: ${err}, info: ${info}`);
@@ -177,7 +188,7 @@ export default {
<action-component
v-if="hasAction"
- :tooltip-text="status.action.title"
+ :tooltip-text="jobActionTooltipText"
:link="status.action.path"
:action-icon="status.action.icon"
data-qa-selector="action_button"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
index c498f12d5c7..4111823e0bb 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
@@ -311,7 +311,7 @@ export default {
this.resetRequestData();
}
- this.updateContent(this.requestData);
+ this.updateContent({ ...this.requestData, page: '1' });
},
changeVisibilityPipelineID(val) {
this.selectedPipelineKeyOption = PipelineKeyOptions.find((e) => val === e.value);
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
index ed32d643c0e..365572f194b 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -2,6 +2,7 @@
import { GlTableLite, GlTooltipDirective } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import Tracking from '~/tracking';
+import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils';
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
import eventHub from '../../event_hub';
import { TRACKING_CATEGORIES } from '../../constants';
@@ -115,6 +116,10 @@ export default {
eventHub.$off('openConfirmationModal', this.setModalData);
},
methods: {
+ getDownstreamPipelines(pipeline) {
+ const downstream = pipeline.triggered;
+ return keepLatestDownstreamPipelines(downstream);
+ },
setModalData(data) {
this.pipelineId = data.pipeline.id;
this.pipeline = data.pipeline;
@@ -171,7 +176,7 @@ export default {
<template #cell(stages)="{ item }">
<pipeline-mini-graph
- :downstream-pipelines="item.triggered"
+ :downstream-pipelines="getDownstreamPipelines(item)"
:pipeline-path="item.path"
:stages="item.details.stages"
:update-dropdown="updateGraphDropdown"
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index 2f37f90e625..820501089ed 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -9,7 +9,6 @@ export const FILTER_TAG_IDENTIFIER = 'tag';
export const SCHEDULE_ORIGIN = 'schedule';
export const NEEDS_PROPERTY = 'needs';
export const EXPLICIT_NEEDS_PROPERTY = 'previousStageJobsOrNeeds';
-export const PIPELINE_GRAPHQL_TYPE = 'Ci::Pipeline';
export const ICONS = {
TAG: 'tag',
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index f00378733fc..ba51347ad69 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -1,6 +1,7 @@
import VueRouter from 'vue-router';
import { createAlert } from '~/flash';
import { __ } from '~/locale';
+import { pipelineTabName } from './constants';
import { createPipelineHeaderApp } from './pipeline_details_header';
import { apolloProvider } from './pipeline_shared_client';
@@ -38,6 +39,12 @@ export default async function initPipelineDetailsBundle() {
routes,
});
+ // We handle the shortcut `pipelines/latest` by forwarding the user to the pipeline graph
+ // tab and changing the route to the correct `pipelines/:id`
+ if (window.location.pathname.endsWith('latest')) {
+ router.replace({ name: pipelineTabName });
+ }
+
try {
const appOptions = createAppOptions(SELECTORS.PIPELINE_TABS, apolloProvider, router);
createPipelineTabs(appOptions);
diff --git a/app/assets/javascripts/pipelines/pipeline_tabs.js b/app/assets/javascripts/pipelines/pipeline_tabs.js
index d0ee6871a48..6360ccc41bc 100644
--- a/app/assets/javascripts/pipelines/pipeline_tabs.js
+++ b/app/assets/javascripts/pipelines/pipeline_tabs.js
@@ -34,6 +34,7 @@ export const createAppOptions = (selector, apolloProvider, router) => {
totalJobCount,
licenseManagementApiUrl,
licenseManagementSettingsPath,
+ licenseScanCount,
licensesApiPath,
canManageLicenses,
summaryEndpoint,
@@ -87,6 +88,7 @@ export const createAppOptions = (selector, apolloProvider, router) => {
totalJobCount,
licenseManagementApiUrl,
licenseManagementSettingsPath,
+ licenseScanCount,
licensesApiPath,
canManageLicenses: parseBoolean(canManageLicenses),
summaryEndpoint,