diff options
Diffstat (limited to 'app/assets/javascripts/pipelines')
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, |