Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
commitb76ae638462ab0f673e5915986070518dd3f9ad3 (patch)
treebdab0533383b52873be0ec0eb4d3c66598ff8b91 /app/assets/javascripts/pipelines
parent434373eabe7b4be9593d18a585fb763f1e5f1a6f (diff)
Add latest changes from gitlab-org/gitlab@14-2-stable-eev14.2.0-rc42
Diffstat (limited to 'app/assets/javascripts/pipelines')
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue15
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component_legacy.vue269
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue48
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue10
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column_legacy.vue91
-rw-r--r--app/assets/javascripts/pipelines/components/graph/perf_utils.js50
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component_legacy.vue112
-rw-r--r--app/assets/javascripts/pipelines/components/graph/utils.js26
-rw-r--r--app/assets/javascripts/pipelines/components/graph_shared/drawing_utils.js2
-rw-r--r--app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue10
-rw-r--r--app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue117
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue9
-rw-r--r--app/assets/javascripts/pipelines/components/parsing_utils.js8
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue5
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue48
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue31
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue144
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_source_token.vue106
-rw-r--r--app/assets/javascripts/pipelines/constants.js16
-rw-r--r--app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js65
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js66
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_mediator.js81
-rw-r--r--app/assets/javascripts/pipelines/services/pipeline_service.js21
-rw-r--r--app/assets/javascripts/pipelines/stores/pipeline_store.js206
26 files changed, 450 insertions, 1116 deletions
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index ea45b5e3ec7..015f0519c72 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -39,10 +39,10 @@ export default {
required: false,
default: false,
},
- pipelineLayers: {
- type: Array,
+ computedPipelineInfo: {
+ type: Object,
required: false,
- default: () => [],
+ default: () => ({}),
},
type: {
type: String,
@@ -81,7 +81,10 @@ export default {
layout() {
return this.isStageView
? this.pipeline.stages
- : generateColumnsFromLayersListMemoized(this.pipeline, this.pipelineLayers);
+ : generateColumnsFromLayersListMemoized(
+ this.pipeline,
+ this.computedPipelineInfo.pipelineLayers,
+ );
},
hasDownstreamPipelines() {
return Boolean(this.pipeline?.downstream?.length > 0);
@@ -92,6 +95,9 @@ export default {
isStageView() {
return this.viewType === STAGE_VIEW;
},
+ linksData() {
+ return this.computedPipelineInfo?.linksData ?? null;
+ },
metricsConfig() {
return {
path: this.configPaths.metricsPath,
@@ -188,6 +194,7 @@ export default {
:container-id="containerId"
:container-measurements="measurements"
:highlighted-job="hoveredJobName"
+ :links-data="linksData"
:metrics-config="metricsConfig"
:show-links="showJobLinks"
:view-type="viewType"
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component_legacy.vue b/app/assets/javascripts/pipelines/components/graph/graph_component_legacy.vue
deleted file mode 100644
index 39d0fa8a8ca..00000000000
--- a/app/assets/javascripts/pipelines/components/graph/graph_component_legacy.vue
+++ /dev/null
@@ -1,269 +0,0 @@
-<script>
-import { GlLoadingIcon } from '@gitlab/ui';
-import { escape, capitalize } from 'lodash';
-import GraphBundleMixin from '../../mixins/graph_pipeline_bundle_mixin';
-import { reportToSentry } from '../../utils';
-import { UPSTREAM, DOWNSTREAM, MAIN } from './constants';
-import LinkedPipelinesColumnLegacy from './linked_pipelines_column_legacy.vue';
-import StageColumnComponentLegacy from './stage_column_component_legacy.vue';
-
-export default {
- name: 'PipelineGraphLegacy',
- components: {
- GlLoadingIcon,
- LinkedPipelinesColumnLegacy,
- StageColumnComponentLegacy,
- },
- mixins: [GraphBundleMixin],
- props: {
- isLoading: {
- type: Boolean,
- required: true,
- },
- pipeline: {
- type: Object,
- required: true,
- },
- isLinkedPipeline: {
- type: Boolean,
- required: false,
- default: false,
- },
- mediator: {
- type: Object,
- required: true,
- },
- type: {
- type: String,
- required: false,
- default: MAIN,
- },
- },
- upstream: UPSTREAM,
- downstream: DOWNSTREAM,
- data() {
- return {
- downstreamMarginTop: null,
- jobName: null,
- pipelineExpanded: {
- jobName: '',
- expanded: false,
- },
- };
- },
- computed: {
- graph() {
- return this.pipeline.details?.stages;
- },
- hasUpstream() {
- return (
- this.type !== this.$options.downstream &&
- this.upstreamPipelines &&
- this.pipeline.triggered_by !== null
- );
- },
- upstreamPipelines() {
- return this.pipeline.triggered_by;
- },
- hasDownstream() {
- return (
- this.type !== this.$options.upstream &&
- this.downstreamPipelines &&
- this.pipeline.triggered.length > 0
- );
- },
- downstreamPipelines() {
- return this.pipeline.triggered;
- },
- expandedUpstream() {
- return (
- this.pipeline.triggered_by &&
- Array.isArray(this.pipeline.triggered_by) &&
- this.pipeline.triggered_by.find((el) => el.isExpanded)
- );
- },
- expandedDownstream() {
- return this.pipeline.triggered && this.pipeline.triggered.find((el) => el.isExpanded);
- },
- pipelineTypeUpstream() {
- return this.type !== this.$options.downstream && this.expandedUpstream;
- },
- pipelineTypeDownstream() {
- return this.type !== this.$options.upstream && this.expandedDownstream;
- },
- pipelineProjectId() {
- return this.pipeline.project.id;
- },
- },
- errorCaptured(err, _vm, info) {
- reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
- },
- methods: {
- capitalizeStageName(name) {
- const escapedName = escape(name);
- return capitalize(escapedName);
- },
- isFirstColumn(index) {
- return index === 0;
- },
- stageConnectorClass(index, stage) {
- let className;
-
- // If it's the first stage column and only has one job
- if (this.isFirstColumn(index) && stage.groups.length === 1) {
- className = 'no-margin';
- } else if (index > 0) {
- // If it is not the first column
- className = 'left-margin';
- }
-
- return className;
- },
- refreshPipelineGraph() {
- this.$emit('refreshPipelineGraph');
- },
- /**
- * CSS class is applied:
- * - if pipeline graph contains only one stage column component
- *
- * @param {number} index
- * @returns {boolean}
- */
- shouldAddRightMargin(index) {
- return !(index === this.graph.length - 1);
- },
- handleClickedDownstream(pipeline, clickedIndex, downstreamNode) {
- /**
- * Calculates the margin top of the clicked downstream pipeline by
- * subtracting the clicked downstream pipelines offsetTop by it's parent's
- * offsetTop and then subtracting 15
- */
- this.downstreamMarginTop = this.calculateMarginTop(downstreamNode, 15);
-
- /**
- * If the expanded trigger is defined and the id is different than the
- * pipeline we clicked, then it means we clicked on a sibling downstream link
- * and we want to reset the pipeline store. Triggering the reset without
- * this condition would mean not allowing downstreams of downstreams to expand
- */
- if (this.expandedDownstream?.id !== pipeline.id) {
- this.$emit('onResetDownstream', this.pipeline, pipeline);
- }
-
- this.$emit('onClickDownstreamPipeline', pipeline);
- },
- calculateMarginTop(downstreamNode, pixelDiff) {
- return `${downstreamNode.offsetTop - downstreamNode.offsetParent.offsetTop - pixelDiff}px`;
- },
- hasOnlyOneJob(stage) {
- return stage.groups.length === 1;
- },
- hasUpstreamColumn(index) {
- return index === 0 && this.hasUpstream;
- },
- setJob(jobName) {
- this.jobName = jobName;
- },
- setPipelineExpanded(jobName, expanded) {
- if (expanded) {
- this.pipelineExpanded = {
- jobName,
- expanded,
- };
- } else {
- this.pipelineExpanded = {
- expanded,
- jobName: '',
- };
- }
- },
- },
-};
-</script>
-<template>
- <div class="build-content middle-block js-pipeline-graph">
- <div
- class="pipeline-visualization pipeline-graph"
- :class="{ 'pipeline-tab-content': !isLinkedPipeline }"
- >
- <div class="gl-w-full">
- <div class="container-fluid container-limited">
- <gl-loading-icon v-if="isLoading" class="m-auto" size="lg" />
- <pipeline-graph-legacy
- v-if="pipelineTypeUpstream"
- :type="$options.upstream"
- class="d-inline-block upstream-pipeline"
- :class="`js-upstream-pipeline-${expandedUpstream.id}`"
- :is-loading="false"
- :pipeline="expandedUpstream"
- :is-linked-pipeline="true"
- :mediator="mediator"
- @onClickUpstreamPipeline="clickUpstreamPipeline"
- @refreshPipelineGraph="requestRefreshPipelineGraph"
- />
-
- <linked-pipelines-column-legacy
- v-if="hasUpstream"
- :type="$options.upstream"
- :linked-pipelines="upstreamPipelines"
- :column-title="__('Upstream')"
- :project-id="pipelineProjectId"
- @linkedPipelineClick="$emit('onClickUpstreamPipeline', $event)"
- />
-
- <ul
- v-if="!isLoading"
- :class="{
- 'inline js-has-linked-pipelines': hasDownstream || hasUpstream,
- }"
- class="stage-column-list align-top"
- >
- <stage-column-component-legacy
- v-for="(stage, index) in graph"
- :key="stage.name"
- :class="{
- 'has-upstream gl-ml-11': hasUpstreamColumn(index),
- 'has-only-one-job': hasOnlyOneJob(stage),
- 'gl-mr-26': shouldAddRightMargin(index),
- }"
- :title="capitalizeStageName(stage.name)"
- :groups="stage.groups"
- :stage-connector-class="stageConnectorClass(index, stage)"
- :is-first-column="isFirstColumn(index)"
- :has-upstream="hasUpstream"
- :action="stage.status.action"
- :job-hovered="jobName"
- :pipeline-expanded="pipelineExpanded"
- @refreshPipelineGraph="refreshPipelineGraph"
- />
- </ul>
-
- <linked-pipelines-column-legacy
- v-if="hasDownstream"
- :type="$options.downstream"
- :linked-pipelines="downstreamPipelines"
- :column-title="__('Downstream')"
- :project-id="pipelineProjectId"
- @linkedPipelineClick="handleClickedDownstream"
- @downstreamHovered="setJob"
- @pipelineExpandToggle="setPipelineExpanded"
- />
-
- <pipeline-graph-legacy
- v-if="pipelineTypeDownstream"
- :type="$options.downstream"
- class="d-inline-block"
- :class="`js-downstream-pipeline-${expandedDownstream.id}`"
- :is-loading="false"
- :pipeline="expandedDownstream"
- :is-linked-pipeline="true"
- :style="{ 'margin-top': downstreamMarginTop }"
- :mediator="mediator"
- @onClickDownstreamPipeline="clickDownstreamPipeline"
- @refreshPipelineGraph="requestRefreshPipelineGraph"
- />
- </div>
- </div>
- </div>
- </div>
-</template>
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 a948a57c144..e995d400907 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
@@ -4,15 +4,15 @@ import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.qu
import getUserCallouts from '~/graphql_shared/queries/get_user_callouts.query.graphql';
import { __ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
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 { listByLayers } from '../parsing_utils';
import { IID_FAILURE, LAYER_VIEW, STAGE_VIEW, VIEW_TYPE_KEY } from './constants';
import PipelineGraph from './graph_component.vue';
import GraphViewSelector from './graph_view_selector.vue';
import {
+ calculatePipelineLayersInfo,
getQueryHeaders,
serializeLoadErrors,
toggleQueryPollingByVisibility,
@@ -31,7 +31,6 @@ export default {
LocalStorageSync,
PipelineGraph,
},
- mixins: [glFeatureFlagMixin()],
inject: {
graphqlResourceEtag: {
default: '',
@@ -50,9 +49,10 @@ export default {
return {
alertType: null,
callouts: [],
+ computedPipelineInfo: null,
currentViewType: STAGE_VIEW,
+ canRefetchHeaderPipeline: false,
pipeline: null,
- pipelineLayers: null,
showAlert: false,
showLinks: false,
};
@@ -78,6 +78,26 @@ export default {
);
},
},
+ headerPipeline: {
+ query: getPipelineQuery,
+ // this query is already being called in header_component.vue, which shares the same cache as this component
+ // the skip here is to prevent sending double network requests on page load
+ skip() {
+ return !this.canRefetchHeaderPipeline;
+ },
+ variables() {
+ return {
+ fullPath: this.pipelineProjectPath,
+ iid: this.pipelineIid,
+ };
+ },
+ update(data) {
+ return data.project?.pipeline || {};
+ },
+ error() {
+ this.reportFailure({ type: LOAD_FAILURE, skipSentry: true });
+ },
+ },
pipeline: {
context() {
return getQueryHeaders(this.graphqlResourceEtag);
@@ -178,7 +198,7 @@ export default {
return this.$apollo.queries.pipeline.loading && !this.pipeline;
},
showGraphViewSelector() {
- return Boolean(this.glFeatures.pipelineGraphLayersView && this.pipeline?.usesNeeds);
+ return this.pipeline?.usesNeeds;
},
},
mounted() {
@@ -192,12 +212,16 @@ export default {
reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
},
methods: {
- getPipelineLayers() {
- if (this.currentViewType === LAYER_VIEW && !this.pipelineLayers) {
- this.pipelineLayers = listByLayers(this.pipeline);
+ getPipelineInfo() {
+ if (this.currentViewType === LAYER_VIEW && !this.computedPipelineInfo) {
+ this.computedPipelineInfo = calculatePipelineLayersInfo(
+ this.pipeline,
+ this.$options.name,
+ this.metricsPath,
+ );
}
- return this.pipelineLayers;
+ return this.computedPipelineInfo;
},
handleTipDismissal() {
try {
@@ -217,6 +241,10 @@ export default {
},
refreshPipelineGraph() {
this.$apollo.queries.pipeline.refetch();
+
+ // this will update the status in header_component since they share the same cache
+ this.canRefetchHeaderPipeline = true;
+ this.$apollo.queries.headerPipeline.refetch();
},
/* eslint-disable @gitlab/require-i18n-strings */
reportFailure({ type, err = 'No error string passed.', skipSentry = false }) {
@@ -262,7 +290,7 @@ export default {
v-if="pipeline"
:config-paths="configPaths"
:pipeline="pipeline"
- :pipeline-layers="getPipelineLayers()"
+ :computed-pipeline-info="getPipelineInfo()"
:show-links="showLinks"
:view-type="graphViewType"
@error="reportFailure"
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 52ee40bd982..d251e0d8bd8 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
@@ -2,10 +2,10 @@
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import { LOAD_FAILURE } from '../../constants';
import { reportToSentry } from '../../utils';
-import { listByLayers } from '../parsing_utils';
import { ONE_COL_WIDTH, UPSTREAM, LAYER_VIEW, STAGE_VIEW } from './constants';
import LinkedPipeline from './linked_pipeline.vue';
import {
+ calculatePipelineLayersInfo,
getQueryHeaders,
serializeLoadErrors,
toggleQueryPollingByVisibility,
@@ -138,7 +138,11 @@ export default {
},
getPipelineLayers(id) {
if (this.viewType === LAYER_VIEW && !this.pipelineLayers[id]) {
- this.pipelineLayers[id] = listByLayers(this.currentPipeline);
+ this.pipelineLayers[id] = calculatePipelineLayersInfo(
+ this.currentPipeline,
+ this.$options.name,
+ this.configPaths.metricsPath,
+ );
}
return this.pipelineLayers[id];
@@ -223,7 +227,7 @@ export default {
class="d-inline-block gl-mt-n2"
:config-paths="configPaths"
:pipeline="currentPipeline"
- :pipeline-layers="getPipelineLayers(pipeline.id)"
+ :computed-pipeline-info="getPipelineLayers(pipeline.id)"
:show-links="showLinks"
:is-linked-pipeline="true"
:view-type="graphViewType"
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column_legacy.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column_legacy.vue
deleted file mode 100644
index 39baeb6e1c3..00000000000
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column_legacy.vue
+++ /dev/null
@@ -1,91 +0,0 @@
-<script>
-import { reportToSentry } from '../../utils';
-import { UPSTREAM } from './constants';
-import LinkedPipeline from './linked_pipeline.vue';
-
-export default {
- components: {
- LinkedPipeline,
- },
- props: {
- columnTitle: {
- type: String,
- required: true,
- },
- linkedPipelines: {
- type: Array,
- required: true,
- },
- type: {
- type: String,
- required: true,
- },
- projectId: {
- type: Number,
- required: true,
- },
- },
- computed: {
- columnClass() {
- const positionValues = {
- right: 'gl-ml-11',
- left: 'gl-mr-7',
- };
- return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`;
- },
- graphPosition() {
- return this.isUpstream ? 'left' : 'right';
- },
- isExpanded() {
- return this.pipeline?.isExpanded || false;
- },
- isUpstream() {
- return this.type === UPSTREAM;
- },
- },
- errorCaptured(err, _vm, info) {
- reportToSentry('linked_pipelines_column_legacy', `error: ${err}, info: ${info}`);
- },
- methods: {
- onPipelineClick(downstreamNode, pipeline, index) {
- this.$emit('linkedPipelineClick', pipeline, index, downstreamNode);
- },
- onDownstreamHovered(jobName) {
- this.$emit('downstreamHovered', jobName);
- },
- onPipelineExpandToggle(jobName, expanded) {
- // Highlighting only applies to downstream pipelines
- if (this.isUpstream) {
- return;
- }
-
- this.$emit('pipelineExpandToggle', jobName, expanded);
- },
- },
-};
-</script>
-
-<template>
- <div :class="columnClass" class="stage-column linked-pipelines-column">
- <div class="stage-name linked-pipelines-column-title">{{ columnTitle }}</div>
- <div v-if="isUpstream" class="cross-project-triangle"></div>
- <ul>
- <li v-for="(pipeline, index) in linkedPipelines" :key="pipeline.id">
- <linked-pipeline
- :class="{
- active: pipeline.isExpanded,
- 'left-connector': pipeline.isExpanded && graphPosition === 'left',
- }"
- :pipeline="pipeline"
- :column-title="columnTitle"
- :project-id="projectId"
- :type="type"
- :expanded="isExpanded"
- @pipelineClicked="onPipelineClick($event, pipeline, index)"
- @downstreamHovered="onDownstreamHovered"
- @pipelineExpandToggle="onPipelineExpandToggle"
- />
- </li>
- </ul>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/perf_utils.js b/app/assets/javascripts/pipelines/components/graph/perf_utils.js
new file mode 100644
index 00000000000..3737a209f5c
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/graph/perf_utils.js
@@ -0,0 +1,50 @@
+import {
+ PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START,
+ PIPELINES_DETAIL_LINKS_MARK_CALCULATE_END,
+ PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION,
+ PIPELINES_DETAIL_LINK_DURATION,
+ PIPELINES_DETAIL_LINKS_TOTAL,
+ PIPELINES_DETAIL_LINKS_JOB_RATIO,
+} from '~/performance/constants';
+
+import { performanceMarkAndMeasure } from '~/performance/utils';
+import { reportPerformance } from '../graph_shared/api';
+
+export const beginPerfMeasure = () => {
+ performanceMarkAndMeasure({ mark: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START });
+};
+
+export const finishPerfMeasureAndSend = (numLinks, numGroups, metricsPath) => {
+ performanceMarkAndMeasure({
+ mark: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_END,
+ measures: [
+ {
+ name: PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION,
+ start: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START,
+ },
+ ],
+ });
+
+ window.requestAnimationFrame(() => {
+ const duration = window.performance.getEntriesByName(
+ PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION,
+ )[0]?.duration;
+
+ if (!duration) {
+ return;
+ }
+
+ const data = {
+ histograms: [
+ { name: PIPELINES_DETAIL_LINK_DURATION, value: duration / 1000 },
+ { name: PIPELINES_DETAIL_LINKS_TOTAL, value: numLinks },
+ {
+ name: PIPELINES_DETAIL_LINKS_JOB_RATIO,
+ value: numLinks / numGroups,
+ },
+ ],
+ };
+
+ reportPerformance(metricsPath, data);
+ });
+};
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component_legacy.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component_legacy.vue
deleted file mode 100644
index cbaf07c05cf..00000000000
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component_legacy.vue
+++ /dev/null
@@ -1,112 +0,0 @@
-<script>
-import { isEmpty, escape } from 'lodash';
-import stageColumnMixin from '../../mixins/stage_column_mixin';
-import { reportToSentry } from '../../utils';
-import ActionComponent from '../jobs_shared/action_component.vue';
-import JobGroupDropdown from './job_group_dropdown.vue';
-import JobItem from './job_item.vue';
-
-export default {
- components: {
- JobItem,
- JobGroupDropdown,
- ActionComponent,
- },
- mixins: [stageColumnMixin],
- props: {
- title: {
- type: String,
- required: true,
- },
- groups: {
- type: Array,
- required: true,
- },
- isFirstColumn: {
- type: Boolean,
- required: false,
- default: false,
- },
- stageConnectorClass: {
- type: String,
- required: false,
- default: '',
- },
- action: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- jobHovered: {
- type: String,
- required: false,
- default: '',
- },
- pipelineExpanded: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- },
- computed: {
- hasAction() {
- return !isEmpty(this.action);
- },
- },
- errorCaptured(err, _vm, info) {
- reportToSentry('stage_column_component_legacy', `error: ${err}, info: ${info}`);
- },
- methods: {
- groupId(group) {
- return `ci-badge-${escape(group.name)}`;
- },
- pipelineActionRequestComplete() {
- this.$emit('refreshPipelineGraph');
- },
- },
-};
-</script>
-<template>
- <li :class="stageConnectorClass" class="stage-column">
- <div class="stage-name position-relative" data-testid="stage-column-title">
- {{ title }}
- <action-component
- v-if="hasAction"
- :action-icon="action.icon"
- :tooltip-text="action.title"
- :link="action.path"
- class="js-stage-action stage-action rounded"
- @pipelineActionRequestComplete="pipelineActionRequestComplete"
- />
- </div>
-
- <div class="builds-container">
- <ul>
- <li
- v-for="(group, index) in groups"
- :id="groupId(group)"
- :key="group.id"
- :class="buildConnnectorClass(index)"
- class="build"
- >
- <div class="curve"></div>
-
- <job-item
- v-if="group.size === 1"
- :job="group.jobs[0]"
- :job-hovered="jobHovered"
- :pipeline-expanded="pipelineExpanded"
- css-class-job-name="build-content"
- @pipelineActionRequestComplete="pipelineActionRequestComplete"
- />
-
- <job-group-dropdown
- v-if="group.size > 1"
- :group="group"
- @pipelineActionRequestComplete="pipelineActionRequestComplete"
- />
- </li>
- </ul>
- </div>
- </li>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/utils.js b/app/assets/javascripts/pipelines/components/graph/utils.js
index 163b3898c28..3da792cb9df 100644
--- a/app/assets/javascripts/pipelines/components/graph/utils.js
+++ b/app/assets/javascripts/pipelines/components/graph/utils.js
@@ -1,7 +1,10 @@
import { isEmpty } from 'lodash';
import Visibility from 'visibilityjs';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { reportToSentry } from '../../utils';
+import { listByLayers } from '../parsing_utils';
import { unwrapStagesWithNeedsAndLookup } from '../unwrapping_utils';
+import { beginPerfMeasure, finishPerfMeasureAndSend } from './perf_utils';
const addMulti = (mainPipelineProjectPath, linkedPipeline) => {
return {
@@ -10,6 +13,28 @@ const addMulti = (mainPipelineProjectPath, linkedPipeline) => {
};
};
+const calculatePipelineLayersInfo = (pipeline, componentName, metricsPath) => {
+ const shouldCollectMetrics = Boolean(metricsPath);
+
+ if (shouldCollectMetrics) {
+ beginPerfMeasure();
+ }
+
+ let layers = null;
+
+ try {
+ layers = listByLayers(pipeline);
+
+ if (shouldCollectMetrics) {
+ finishPerfMeasureAndSend(layers.linksData.length, layers.numGroups, metricsPath);
+ }
+ } catch (err) {
+ reportToSentry(componentName, err);
+ }
+
+ return layers;
+};
+
/* eslint-disable @gitlab/require-i18n-strings */
const getQueryHeaders = (etagResource) => {
return {
@@ -106,6 +131,7 @@ const unwrapPipelineData = (mainPipelineProjectPath, data) => {
const validateConfigPaths = (value) => value.graphqlResourceEtag?.length > 0;
export {
+ calculatePipelineLayersInfo,
getQueryHeaders,
serializeGqlErr,
serializeLoadErrors,
diff --git a/app/assets/javascripts/pipelines/components/graph_shared/drawing_utils.js b/app/assets/javascripts/pipelines/components/graph_shared/drawing_utils.js
index 83f2466f0bf..d6d9ea94c13 100644
--- a/app/assets/javascripts/pipelines/components/graph_shared/drawing_utils.js
+++ b/app/assets/javascripts/pipelines/components/graph_shared/drawing_utils.js
@@ -13,7 +13,7 @@ export const createUniqueLinkId = (stageName, jobName) => `${stageName}-${jobNam
* @returns {Array} Links that contain all the information about them
*/
-export const generateLinksData = ({ links }, containerID, modifier = '') => {
+export const generateLinksData = (links, containerID, modifier = '') => {
const containerEl = document.getElementById(containerID);
return links.map((link) => {
diff --git a/app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue b/app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue
index 5c775df7b48..1189c2ebad8 100644
--- a/app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue
+++ b/app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue
@@ -17,8 +17,8 @@ export default {
type: Object,
required: true,
},
- parsedData: {
- type: Object,
+ linksData: {
+ type: Array,
required: true,
},
pipelineId: {
@@ -95,7 +95,7 @@ export default {
highlightedJobs(jobs) {
this.$emit('highlightedJobsChange', jobs);
},
- parsedData() {
+ linksData() {
this.calculateLinkData();
},
viewType() {
@@ -112,7 +112,7 @@ export default {
reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
},
mounted() {
- if (!isEmpty(this.parsedData)) {
+ if (!isEmpty(this.linksData)) {
this.calculateLinkData();
}
},
@@ -122,7 +122,7 @@ export default {
},
calculateLinkData() {
try {
- this.links = generateLinksData(this.parsedData, this.containerId, `-${this.pipelineId}`);
+ this.links = generateLinksData(this.linksData, this.containerId, `-${this.pipelineId}`);
} catch (err) {
this.$emit('error', { type: DRAW_FAILURE, reportToSentry: false });
reportToSentry(this.$options.name, err);
diff --git a/app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue b/app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue
index 81409752621..ef24694e494 100644
--- a/app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue
+++ b/app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue
@@ -1,20 +1,16 @@
<script>
-import { isEmpty } from 'lodash';
-import { __ } from '~/locale';
-import {
- PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START,
- PIPELINES_DETAIL_LINKS_MARK_CALCULATE_END,
- PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION,
- PIPELINES_DETAIL_LINK_DURATION,
- PIPELINES_DETAIL_LINKS_TOTAL,
- PIPELINES_DETAIL_LINKS_JOB_RATIO,
-} from '~/performance/constants';
-import { performanceMarkAndMeasure } from '~/performance/utils';
+import { memoize } from 'lodash';
import { reportToSentry } from '../../utils';
import { parseData } from '../parsing_utils';
-import { reportPerformance } from './api';
import LinksInner from './links_inner.vue';
+const parseForLinksBare = (pipeline) => {
+ const arrayOfJobs = pipeline.flatMap(({ groups }) => groups);
+ return parseData(arrayOfJobs).links;
+};
+
+const parseForLinks = memoize(parseForLinksBare);
+
export default {
name: 'LinksLayer',
components: {
@@ -29,10 +25,10 @@ export default {
type: Array,
required: true,
},
- metricsConfig: {
- type: Object,
+ linksData: {
+ type: Array,
required: false,
- default: () => ({}),
+ default: () => [],
},
showLinks: {
type: Boolean,
@@ -40,30 +36,16 @@ export default {
default: true,
},
},
- data() {
- return {
- alertDismissed: false,
- parsedData: {},
- showLinksOverride: false,
- };
- },
- i18n: {
- showLinksAnyways: __('Show links anyways'),
- tooManyJobs: __(
- 'This graph has a large number of jobs and showing the links between them may have performance implications.',
- ),
- },
computed: {
containerZero() {
return !this.containerMeasurements.width || !this.containerMeasurements.height;
},
- numGroups() {
- return this.pipelineData.reduce((acc, { groups }) => {
- return acc + Number(groups.length);
- }, 0);
- },
- shouldCollectMetrics() {
- return this.metricsConfig.collectMetrics && this.metricsConfig.path;
+ getLinksData() {
+ if (this.linksData.length > 0) {
+ return this.linksData;
+ }
+
+ return parseForLinks(this.pipelineData);
},
showLinkedLayers() {
return this.showLinks && !this.containerZero;
@@ -72,77 +54,14 @@ export default {
errorCaptured(err, _vm, info) {
reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
},
- mounted() {
- if (!isEmpty(this.pipelineData)) {
- window.requestAnimationFrame(() => {
- this.prepareLinkData();
- });
- }
- },
- methods: {
- beginPerfMeasure() {
- if (this.shouldCollectMetrics) {
- performanceMarkAndMeasure({ mark: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START });
- }
- },
- finishPerfMeasureAndSend(numLinks) {
- if (this.shouldCollectMetrics) {
- performanceMarkAndMeasure({
- mark: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_END,
- measures: [
- {
- name: PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION,
- start: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START,
- },
- ],
- });
- }
-
- window.requestAnimationFrame(() => {
- const duration = window.performance.getEntriesByName(
- PIPELINES_DETAIL_LINKS_MEASURE_CALCULATION,
- )[0]?.duration;
-
- if (!duration) {
- return;
- }
-
- const data = {
- histograms: [
- { name: PIPELINES_DETAIL_LINK_DURATION, value: duration / 1000 },
- { name: PIPELINES_DETAIL_LINKS_TOTAL, value: numLinks },
- {
- name: PIPELINES_DETAIL_LINKS_JOB_RATIO,
- value: numLinks / this.numGroups,
- },
- ],
- };
-
- reportPerformance(this.metricsConfig.path, data);
- });
- },
- prepareLinkData() {
- this.beginPerfMeasure();
- let numLinks;
- try {
- const arrayOfJobs = this.pipelineData.flatMap(({ groups }) => groups);
- this.parsedData = parseData(arrayOfJobs);
- numLinks = this.parsedData.links.length;
- } catch (err) {
- reportToSentry(this.$options.name, err);
- }
- this.finishPerfMeasureAndSend(numLinks);
- },
- },
};
</script>
<template>
<links-inner
v-if="showLinkedLayers"
:container-measurements="containerMeasurements"
- :parsed-data="parsedData"
+ :links-data="getLinksData"
:pipeline-data="pipelineData"
- :total-groups="numGroups"
v-bind="$attrs"
v-on="$listeners"
>
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index b7500ef00b0..5db2b604956 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -143,13 +143,6 @@ export default {
return cancelable && userPermissions.updatePipeline;
},
},
- watch: {
- isFinished(finished) {
- if (finished) {
- this.$apollo.queries.pipeline.stopPolling();
- }
- },
- },
methods: {
reportFailure(errorType) {
this.failureType = errorType;
@@ -218,7 +211,7 @@ export default {
};
</script>
<template>
- <div class="pipeline-header-container">
+ <div class="js-pipeline-header-container">
<gl-alert v-if="hasError" :variant="failure.variant">{{ failure.text }}</gl-alert>
<ci-header
v-if="shouldRenderContent"
diff --git a/app/assets/javascripts/pipelines/components/parsing_utils.js b/app/assets/javascripts/pipelines/components/parsing_utils.js
index b36c9c0d049..7e7f0572faf 100644
--- a/app/assets/javascripts/pipelines/components/parsing_utils.js
+++ b/app/assets/javascripts/pipelines/components/parsing_utils.js
@@ -175,7 +175,7 @@ export const listByLayers = ({ stages }) => {
const parsedData = parseData(arrayOfJobs);
const dataWithLayers = createSankey()(parsedData);
- return dataWithLayers.nodes.reduce((acc, { layer, name }) => {
+ const pipelineLayers = dataWithLayers.nodes.reduce((acc, { layer, name }) => {
/* sort groups by layer */
if (!acc[layer]) {
@@ -186,6 +186,12 @@ export const listByLayers = ({ stages }) => {
return acc;
}, []);
+
+ return {
+ linksData: parsedData.links,
+ numGroups: arrayOfJobs.length,
+ pipelineLayers,
+ };
};
export const generateColumnsFromLayersListBare = ({ stages, stagesLookup }, pipelineLayers) => {
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue
index 5e18f636b52..40ee071f1f5 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue
@@ -16,6 +16,7 @@ export const i18n = {
downloadArtifact: __('Download %{name} artifact'),
artifactSectionHeader: __('Download artifacts'),
artifactsFetchErrorMessage: s__('Pipelines|Could not load artifacts.'),
+ emptyArtifactsMessage: __('No artifacts found'),
};
export default {
@@ -99,6 +100,10 @@ export default {
<gl-loading-icon v-if="isLoading" size="sm" />
+ <gl-dropdown-item v-if="!artifacts.length" data-testid="artifacts-empty-message">
+ {{ $options.i18n.emptyArtifactsMessage }}
+ </gl-dropdown-item>
+
<gl-dropdown-item
v-for="(artifact, i) in artifacts"
:key="i"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
index 85ee44f427d..b6c178d20b0 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
@@ -95,10 +95,10 @@ export default {
:title="$options.i18n.cancelTitle"
:loading="isCancelling"
:disabled="isCancelling"
- icon="close"
+ icon="cancel"
variant="danger"
category="primary"
- class="js-pipelines-cancel-button"
+ class="js-pipelines-cancel-button gl-ml-1"
@click="handleCancelClick"
/>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
index fc8f31c5b7e..e2f30d5a8e6 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
@@ -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
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
index e3373178239..e7ff5449331 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
@@ -1,12 +1,17 @@
<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>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue
index de3f783ac84..0b70e74b8ff 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue
@@ -4,6 +4,7 @@ import { map } from 'lodash';
import { s__ } from '~/locale';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import PipelineBranchNameToken from './tokens/pipeline_branch_name_token.vue';
+import PipelineSourceToken from './tokens/pipeline_source_token.vue';
import PipelineStatusToken from './tokens/pipeline_status_token.vue';
import PipelineTagNameToken from './tokens/pipeline_tag_name_token.vue';
import PipelineTriggerAuthorToken from './tokens/pipeline_trigger_author_token.vue';
@@ -13,6 +14,7 @@ export default {
branchType: 'ref',
tagType: 'tag',
statusType: 'status',
+ sourceType: 'source',
defaultTokensLength: 1,
components: {
GlFilteredSearch,
@@ -37,7 +39,7 @@ export default {
return this.value.map((i) => i.type);
},
tokens() {
- return [
+ const tokens = [
{
type: this.$options.userType,
icon: 'user',
@@ -76,6 +78,19 @@ export default {
operators: OPERATOR_IS_ONLY,
},
];
+
+ if (gon.features.pipelineSourceFilter) {
+ tokens.push({
+ type: this.$options.sourceType,
+ icon: 'trigger-source',
+ title: s__('Pipeline|Source'),
+ unique: true,
+ token: PipelineSourceToken,
+ operators: OPERATOR_IS_ONLY,
+ });
+ }
+
+ return tokens;
},
parsedParams() {
return map(this.params, (val, key) => ({
@@ -101,12 +116,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>
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 47fc7023222..2475d958e3c 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -17,65 +17,10 @@ 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-15p',
- 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-20p',
- thAttr: { 'data-testid': 'actions-th' },
- },
- ],
components: {
GlTable,
+ LinkedPipelinesMiniList: () =>
+ import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'),
PipelinesCommit,
PipelineMiniGraph,
PipelineOperations,
@@ -107,6 +52,10 @@ export default {
type: String,
required: true,
},
+ pipelineKeyOption: {
+ type: Object,
+ required: true,
+ },
},
data() {
return {
@@ -116,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;
@@ -146,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' }"
@@ -167,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 }">
@@ -182,12 +197,23 @@ export default {
<div class="stage-cell">
<!-- This empty div should be removed, see https://gitlab.com/gitlab-org/gitlab/-/issues/323488 -->
<div></div>
+ <linked-pipelines-mini-list
+ v-if="item.triggered_by"
+ :triggered-by="[item.triggered_by]"
+ data-testid="mini-graph-upstream"
+ />
<pipeline-mini-graph
v-if="item.details && item.details.stages && item.details.stages.length > 0"
+ class="gl-display-inline"
:stages="item.details.stages"
:update-dropdown="updateGraphDropdown"
@pipelineActionRequestComplete="onPipelineActionRequestComplete"
/>
+ <linked-pipelines-mini-list
+ v-if="item.triggered.length"
+ :triggered="item.triggered"
+ data-testid="mini-graph-downstream"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_source_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_source_token.vue
new file mode 100644
index 00000000000..71efa8b2ab4
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_source_token.vue
@@ -0,0 +1,106 @@
+<script>
+import { GlFilteredSearchToken, GlFilteredSearchSuggestion } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ components: {
+ GlFilteredSearchToken,
+ GlFilteredSearchSuggestion,
+ },
+ props: {
+ config: {
+ type: Object,
+ required: true,
+ },
+ value: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ sources() {
+ return [
+ {
+ text: s__('Pipeline|Source|Push'),
+ value: 'push',
+ },
+ {
+ text: s__('Pipeline|Source|Web'),
+ value: 'web',
+ },
+ {
+ text: s__('Pipeline|Source|Trigger'),
+ value: 'trigger',
+ },
+ {
+ text: s__('Pipeline|Source|Schedule'),
+ value: 'schedule',
+ },
+ {
+ text: s__('Pipeline|Source|API'),
+ value: 'api',
+ },
+ {
+ text: s__('Pipeline|Source|External'),
+ value: 'external',
+ },
+ {
+ text: s__('Pipeline|Source|Pipeline'),
+ value: 'pipeline',
+ },
+ {
+ text: s__('Pipeline|Source|Chat'),
+ value: 'chat',
+ },
+ {
+ text: s__('Pipeline|Source|Web IDE'),
+ value: 'webide',
+ },
+ {
+ text: s__('Pipeline|Source|Merge Request'),
+ value: 'merge_request_event',
+ },
+ {
+ text: s__('Pipeline|Source|External Pull Request'),
+ value: 'external_pull_request_event',
+ },
+ {
+ text: s__('Pipeline|Source|Parent Pipeline'),
+ value: 'parent_pipeline',
+ },
+ {
+ text: s__('Pipeline|Source|On-Demand DAST Scan'),
+ value: 'ondemand_dast_scan',
+ },
+ {
+ text: s__('Pipeline|Source|On-Demand DAST Validation'),
+ value: 'ondemand_dast_validation',
+ },
+ ];
+ },
+ findActiveSource() {
+ return this.sources.find((source) => source.value === this.value.data);
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-filtered-search-token v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
+ <template #view>
+ <div class="gl-display-flex gl-align-items-center">
+ <span>{{ findActiveSource.text }}</span>
+ </div>
+ </template>
+
+ <template #suggestions>
+ <gl-filtered-search-suggestion
+ v-for="source in sources"
+ :key="source.value"
+ :value="source.value"
+ >
+ {{ source.text }}
+ </gl-filtered-search-suggestion>
+ </template>
+ </gl-filtered-search-token>
+</template>
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index 21b114825a6..5678b613ec6 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -4,7 +4,7 @@ export const CANCEL_REQUEST = 'CANCEL_REQUEST';
export const LAYOUT_CHANGE_DELAY = 300;
export const FILTER_PIPELINES_SEARCH_DELAY = 200;
export const ANY_TRIGGER_AUTHOR = 'Any';
-export const SUPPORTED_FILTER_PARAMETERS = ['username', 'ref', 'status'];
+export const SUPPORTED_FILTER_PARAMETERS = ['username', 'ref', 'status', 'source'];
export const FILTER_TAG_IDENTIFIER = 'tag';
export const SCHEDULE_ORIGIN = 'schedule';
@@ -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',
+ },
+];
diff --git a/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js
deleted file mode 100644
index 5c34f4e4f7e..00000000000
--- a/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import createFlash from '~/flash';
-import { __ } from '~/locale';
-
-export default {
- methods: {
- getExpandedPipelines(pipeline) {
- this.mediator.service
- .getPipeline(this.mediator.getExpandedParameters())
- .then((response) => {
- this.mediator.store.toggleLoading(pipeline);
- this.mediator.store.storePipeline(response.data);
- this.mediator.poll.enable({ data: this.mediator.getExpandedParameters() });
- })
- .catch(() => {
- this.mediator.store.toggleLoading(pipeline);
- createFlash({
- message: __('An error occurred while fetching the pipeline.'),
- });
- });
- },
- /**
- * Called when a linked pipeline is clicked.
- *
- * If the pipeline is collapsed we will start polling it & we will reset the other pipelines.
- * If the pipeline is expanded we will close it.
- *
- * @param {String} method Method to fetch the pipeline
- * @param {String} storeKey Store property that will be updates
- * @param {String} resetStoreKey Store key for the visible pipeline that will need to be reset
- * @param {Object} pipeline The clicked pipeline
- */
- clickPipeline(pipeline, openMethod, closeMethod) {
- if (!pipeline.isExpanded) {
- this.mediator.store[openMethod](pipeline);
- this.mediator.store.toggleLoading(pipeline);
- this.mediator.poll.stop();
-
- this.getExpandedPipelines(pipeline);
- } else {
- this.mediator.store[closeMethod](pipeline);
- this.mediator.poll.stop();
-
- this.mediator.poll.enable({ data: this.mediator.getExpandedParameters() });
- }
- },
- resetDownstreamPipelines(parentPipeline, pipeline) {
- this.mediator.store.resetTriggeredPipelines(parentPipeline, pipeline);
- },
- clickUpstreamPipeline(pipeline) {
- this.clickPipeline(pipeline, 'openPipeline', 'closePipeline');
- },
- clickDownstreamPipeline(pipeline) {
- this.clickPipeline(pipeline, 'openPipeline', 'closePipeline');
- },
- requestRefreshPipelineGraph() {
- // When an action is clicked
- // (whether in the dropdown or in the main nodes, we refresh the big graph)
- this.mediator.refreshPipeline().catch(() =>
- createFlash({
- message: __('An error occurred while making the request.'),
- }),
- );
- },
- },
-};
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index e8d5ed175ba..c6e767d5424 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -3,15 +3,12 @@ import createFlash from '~/flash';
import { parseBoolean } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import Translate from '~/vue_shared/translate';
-import PipelineGraphLegacy from './components/graph/graph_component_legacy.vue';
import TestReports from './components/test_reports/test_reports.vue';
-import GraphBundleMixin from './mixins/graph_pipeline_bundle_mixin';
import createDagApp from './pipeline_details_dag';
import { createPipelinesDetailApp } from './pipeline_details_graph';
import { createPipelineHeaderApp } from './pipeline_details_header';
import { apolloProvider } from './pipeline_shared_client';
import createTestReportsStore from './stores/test_reports';
-import { reportToSentry } from './utils';
Vue.use(Translate);
@@ -22,44 +19,6 @@ const SELECTORS = {
PIPELINE_TESTS: '#js-pipeline-tests-detail',
};
-const createLegacyPipelinesDetailApp = (mediator) => {
- if (!document.querySelector(SELECTORS.PIPELINE_GRAPH)) {
- return;
- }
- // eslint-disable-next-line no-new
- new Vue({
- el: SELECTORS.PIPELINE_GRAPH,
- components: {
- PipelineGraphLegacy,
- },
- mixins: [GraphBundleMixin],
- data() {
- return {
- mediator,
- };
- },
- errorCaptured(err, _vm, info) {
- reportToSentry('pipeline_details_bundle_legacy_details', `error: ${err}, info: ${info}`);
- },
- render(createElement) {
- return createElement('pipeline-graph-legacy', {
- props: {
- isLoading: this.mediator.state.isLoading,
- pipeline: this.mediator.store.state.pipeline,
- mediator: this.mediator,
- },
- on: {
- refreshPipelineGraph: this.requestRefreshPipelineGraph,
- onResetDownstream: (parentPipeline, pipeline) =>
- this.resetDownstreamPipelines(parentPipeline, pipeline),
- onClickUpstreamPipeline: (pipeline) => this.clickUpstreamPipeline(pipeline),
- onClickDownstreamPipeline: (pipeline) => this.clickDownstreamPipeline(pipeline),
- },
- });
- },
- });
-};
-
const createTestDetails = () => {
const el = document.querySelector(SELECTORS.PIPELINE_TESTS);
const { blobPath, emptyStateImagePath, hasTestReport, summaryEndpoint, suiteEndpoint } =
@@ -88,9 +47,6 @@ const createTestDetails = () => {
};
export default async function initPipelineDetailsBundle() {
- const canShowNewPipelineDetails =
- gon.features.graphqlPipelineDetails || gon.features.graphqlPipelineDetailsUsers;
-
const { dataset } = document.querySelector(SELECTORS.PIPELINE_DETAILS);
try {
@@ -101,22 +57,12 @@ export default async function initPipelineDetailsBundle() {
});
}
- if (canShowNewPipelineDetails) {
- try {
- createPipelinesDetailApp(SELECTORS.PIPELINE_GRAPH, apolloProvider, dataset);
- } catch {
- createFlash({
- message: __('An error occurred while loading the pipeline.'),
- });
- }
- } else {
- const { default: PipelinesMediator } = await import(
- /* webpackChunkName: 'PipelinesMediator' */ './pipeline_details_mediator'
- );
- const mediator = new PipelinesMediator({ endpoint: dataset.endpoint });
- mediator.fetchPipeline();
-
- createLegacyPipelinesDetailApp(mediator);
+ try {
+ createPipelinesDetailApp(SELECTORS.PIPELINE_GRAPH, apolloProvider, dataset);
+ } catch {
+ createFlash({
+ message: __('An error occurred while loading the pipeline.'),
+ });
}
createDagApp(apolloProvider);
diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediator.js b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
deleted file mode 100644
index 72c4fedc64c..00000000000
--- a/app/assets/javascripts/pipelines/pipeline_details_mediator.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import Visibility from 'visibilityjs';
-import createFlash from '~/flash';
-import Poll from '../lib/utils/poll';
-import { __ } from '../locale';
-import PipelineService from './services/pipeline_service';
-import PipelineStore from './stores/pipeline_store';
-
-export default class pipelinesMediator {
- constructor(options = {}) {
- this.options = options;
- this.store = new PipelineStore();
- this.service = new PipelineService(options.endpoint);
-
- this.state = {};
- this.state.isLoading = false;
- }
-
- fetchPipeline() {
- this.poll = new Poll({
- resource: this.service,
- method: 'getPipeline',
- data: this.store.state.expandedPipelines ? this.getExpandedParameters() : undefined,
- successCallback: this.successCallback.bind(this),
- errorCallback: this.errorCallback.bind(this),
- });
-
- if (!Visibility.hidden()) {
- this.state.isLoading = true;
- this.poll.makeRequest();
- } else {
- this.refreshPipeline();
- }
-
- Visibility.change(() => {
- if (!Visibility.hidden()) {
- this.poll.restart();
- } else {
- this.stopPipelinePoll();
- }
- });
- }
-
- successCallback(response) {
- this.state.isLoading = false;
- this.store.storePipeline(response.data);
- }
-
- errorCallback() {
- this.state.isLoading = false;
- createFlash({
- message: __('An error occurred while fetching the pipeline.'),
- });
- }
-
- refreshPipeline() {
- this.stopPipelinePoll();
-
- return this.service
- .getPipeline()
- .then((response) => this.successCallback(response))
- .catch(() => this.errorCallback())
- .finally(() =>
- this.poll.restart(
- this.store.state.expandedPipelines ? this.getExpandedParameters() : undefined,
- ),
- );
- }
-
- stopPipelinePoll() {
- this.poll.stop();
- }
-
- /**
- * Backend expects paramets in the following format: `expanded[]=id&expanded[]=id`
- */
- getExpandedParameters() {
- return {
- expanded: this.store.state.expandedPipelines,
- };
- }
-}
diff --git a/app/assets/javascripts/pipelines/services/pipeline_service.js b/app/assets/javascripts/pipelines/services/pipeline_service.js
deleted file mode 100644
index ba2830ec596..00000000000
--- a/app/assets/javascripts/pipelines/services/pipeline_service.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import axios from '../../lib/utils/axios_utils';
-
-export default class PipelineService {
- constructor(endpoint) {
- this.pipeline = endpoint;
- }
-
- getPipeline(params) {
- return axios.get(this.pipeline, { params });
- }
-
- // eslint-disable-next-line class-methods-use-this
- deleteAction(endpoint) {
- return axios.delete(`${endpoint}.json`);
- }
-
- // eslint-disable-next-line class-methods-use-this
- postAction(endpoint) {
- return axios.post(`${endpoint}.json`);
- }
-}
diff --git a/app/assets/javascripts/pipelines/stores/pipeline_store.js b/app/assets/javascripts/pipelines/stores/pipeline_store.js
deleted file mode 100644
index 1f804a107a8..00000000000
--- a/app/assets/javascripts/pipelines/stores/pipeline_store.js
+++ /dev/null
@@ -1,206 +0,0 @@
-import Vue from 'vue';
-
-export default class PipelineStore {
- constructor() {
- this.state = {};
- this.state.pipeline = {};
- this.state.expandedPipelines = [];
- }
- /**
- * For the triggered pipelines adds the `isExpanded` key
- *
- * For the triggered_by pipeline adds the `isExpanded` key
- * and saves it as an array
- *
- * @param {Object} pipeline
- */
- storePipeline(pipeline = {}) {
- const pipelineCopy = { ...pipeline };
-
- if (pipelineCopy.triggered_by) {
- pipelineCopy.triggered_by = [pipelineCopy.triggered_by];
-
- const oldTriggeredBy =
- this.state.pipeline &&
- this.state.pipeline.triggered_by &&
- this.state.pipeline.triggered_by[0];
-
- this.parseTriggeredByPipelines(oldTriggeredBy, pipelineCopy.triggered_by[0]);
- }
-
- if (pipelineCopy.triggered && pipelineCopy.triggered.length) {
- pipelineCopy.triggered.forEach((el) => {
- const oldPipeline =
- this.state.pipeline &&
- this.state.pipeline.triggered &&
- this.state.pipeline.triggered.find((element) => element.id === el.id);
-
- this.parseTriggeredPipelines(oldPipeline, el);
- });
- }
-
- this.state.pipeline = pipelineCopy;
- }
-
- /**
- * Recursiverly parses the triggered by pipelines.
- *
- * Sets triggered_by as an array, there is always only 1 triggered_by pipeline.
- * Adds key `isExpanding`
- * Keeps old isExpading value due to polling
- *
- * @param {Array} parentPipeline
- * @param {Object} pipeline
- */
- parseTriggeredByPipelines(oldPipeline = {}, newPipeline) {
- // keep old value in case it's opened because we're polling
- Vue.set(newPipeline, 'isExpanded', oldPipeline.isExpanded || false);
- // add isLoading property
- Vue.set(newPipeline, 'isLoading', false);
-
- // Because there can only ever be one `triggered_by` for any given pipeline,
- // the API returns an object for the value instead of an Array. However,
- // it's easier to deal with an array in the FE so we convert it.
- if (newPipeline.triggered_by) {
- if (!Array.isArray(newPipeline.triggered_by)) {
- Object.assign(newPipeline, { triggered_by: [newPipeline.triggered_by] });
- }
-
- if (newPipeline.triggered_by?.length > 0) {
- newPipeline.triggered_by.forEach((el) => {
- const oldTriggeredBy = oldPipeline.triggered_by?.find((element) => element.id === el.id);
- this.parseTriggeredPipelines(oldTriggeredBy, el);
- });
- }
- }
- }
-
- /**
- * Recursively parses the triggered pipelines
- * @param {Array} parentPipeline
- * @param {Object} pipeline
- */
- parseTriggeredPipelines(oldPipeline = {}, newPipeline) {
- // keep old value in case it's opened because we're polling
- Vue.set(newPipeline, 'isExpanded', oldPipeline.isExpanded || false);
-
- // add isLoading property
- Vue.set(newPipeline, 'isLoading', false);
-
- if (newPipeline.triggered && newPipeline.triggered.length > 0) {
- newPipeline.triggered.forEach((el) => {
- const oldTriggered =
- oldPipeline.triggered && oldPipeline.triggered.find((element) => element.id === el.id);
- this.parseTriggeredPipelines(oldTriggered, el);
- });
- }
- }
-
- /**
- * Recursively resets all triggered by pipelines
- *
- * @param {Object} pipeline
- */
- resetTriggeredByPipeline(parentPipeline, pipeline) {
- parentPipeline.triggered_by.forEach((el) => this.closePipeline(el));
-
- if (pipeline.triggered_by && pipeline.triggered_by) {
- this.resetTriggeredByPipeline(pipeline, pipeline.triggered_by);
- }
- }
-
- /**
- * Opens the clicked pipeline and closes all other ones.
- * @param {Object} pipeline
- */
- openTriggeredByPipeline(parentPipeline, pipeline) {
- // first we need to reset all triggeredBy pipelines
- this.resetTriggeredByPipeline(parentPipeline, pipeline);
-
- this.openPipeline(pipeline);
- }
-
- /**
- * On click, will close the given pipeline and all nested triggered by pipelines
- *
- * @param {Object} pipeline
- */
- closeTriggeredByPipeline(pipeline) {
- this.closePipeline(pipeline);
-
- if (pipeline.triggered_by && pipeline.triggered_by.length) {
- pipeline.triggered_by.forEach((triggeredBy) => this.closeTriggeredByPipeline(triggeredBy));
- }
- }
-
- /**
- * Recursively closes all triggered pipelines for the given one.
- *
- * @param {Object} pipeline
- */
- resetTriggeredPipelines(parentPipeline, pipeline) {
- parentPipeline.triggered.forEach((el) => this.closePipeline(el));
-
- if (pipeline.triggered && pipeline.triggered.length) {
- pipeline.triggered.forEach((el) => this.resetTriggeredPipelines(pipeline, el));
- }
- }
-
- /**
- * Opens the clicked triggered pipeline and closes all other ones.
- *
- * @param {Object} pipeline
- */
- openTriggeredPipeline(parentPipeline, pipeline) {
- this.resetTriggeredPipelines(parentPipeline, pipeline);
-
- this.openPipeline(pipeline);
- }
-
- /**
- * On click, will close the given pipeline and all the nested triggered ones
- * @param {Object} pipeline
- */
- closeTriggeredPipeline(pipeline) {
- this.closePipeline(pipeline);
-
- if (pipeline.triggered && pipeline.triggered.length) {
- pipeline.triggered.forEach((triggered) => this.closeTriggeredPipeline(triggered));
- }
- }
-
- /**
- * Utility function, Closes the given pipeline
- * @param {Object} pipeline
- */
- closePipeline(pipeline) {
- Vue.set(pipeline, 'isExpanded', false);
- // remove the pipeline from the parameters
- this.removeExpandedPipelineToRequestData(pipeline.id);
- }
-
- /**
- * Utility function, Opens the given pipeline
- * @param {Object} pipeline
- */
- openPipeline(pipeline) {
- Vue.set(pipeline, 'isExpanded', true);
- // add the pipeline to the parameters
- this.addExpandedPipelineToRequestData(pipeline.id);
- }
- // eslint-disable-next-line class-methods-use-this
- toggleLoading(pipeline) {
- Vue.set(pipeline, 'isLoading', !pipeline.isLoading);
- }
-
- addExpandedPipelineToRequestData(id) {
- this.state.expandedPipelines.push(id);
- }
-
- removeExpandedPipelineToRequestData(id) {
- this.state.expandedPipelines.splice(
- this.state.expandedPipelines.findIndex((el) => el === id),
- 1,
- );
- }
-}