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:
Diffstat (limited to 'app/assets/javascripts/pipelines')
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue24
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue63
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue33
-rw-r--r--app/assets/javascripts/pipelines/components/graph/utils.js97
-rw-r--r--app/assets/javascripts/pipelines/components/graph_shared/api.js8
-rw-r--r--app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue72
-rw-r--r--app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue7
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue15
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue12
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue54
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue119
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue152
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue12
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue92
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue10
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_commit.vue85
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_manual_actions.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue)1
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue37
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue212
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue61
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/stage.vue234
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue41
-rw-r--r--app/assets/javascripts/pipelines/constants.js3
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js3
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_graph.js9
-rw-r--r--app/assets/javascripts/pipelines/pipelines_index.js4
27 files changed, 1020 insertions, 442 deletions
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 93156d5d05b..fc0a8b07e7f 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -4,7 +4,7 @@ import LinksLayer from '../graph_shared/links_layer.vue';
import { DOWNSTREAM, MAIN, UPSTREAM, ONE_COL_WIDTH } from './constants';
import LinkedPipelinesColumn from './linked_pipelines_column.vue';
import StageColumnComponent from './stage_column_component.vue';
-import { reportToSentry } from './utils';
+import { reportToSentry, validateConfigPaths } from './utils';
export default {
name: 'PipelineGraph',
@@ -15,15 +15,20 @@ export default {
StageColumnComponent,
},
props: {
- isLinkedPipeline: {
- type: Boolean,
- required: false,
- default: false,
+ configPaths: {
+ type: Object,
+ required: true,
+ validator: validateConfigPaths,
},
pipeline: {
type: Object,
required: true,
},
+ isLinkedPipeline: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
type: {
type: String,
required: false,
@@ -66,6 +71,12 @@ export default {
hasUpstreamPipelines() {
return Boolean(this.pipeline?.upstream?.length > 0);
},
+ metricsConfig() {
+ return {
+ path: this.configPaths.metricsPath,
+ collectMetrics: true,
+ };
+ },
// The show downstream check prevents showing redundant linked columns
showDownstreamPipelines() {
return (
@@ -131,6 +142,7 @@ export default {
<template #upstream>
<linked-pipelines-column
v-if="showUpstreamPipelines"
+ :config-paths="configPaths"
:linked-pipelines="upstreamPipelines"
:column-title="__('Upstream')"
:type="$options.pipelineTypeConstants.UPSTREAM"
@@ -145,6 +157,7 @@ export default {
:container-id="containerId"
:container-measurements="measurements"
:highlighted-job="hoveredJobName"
+ :metrics-config="metricsConfig"
default-link-color="gl-stroke-transparent"
@error="onError"
@highlightedJobsChange="updateHighlightedJobs"
@@ -170,6 +183,7 @@ export default {
<linked-pipelines-column
v-if="showDownstreamPipelines"
class="gl-mr-6"
+ :config-paths="configPaths"
:linked-pipelines="downstreamPipelines"
:column-title="__('Downstream')"
:type="$options.pipelineTypeConstants.DOWNSTREAM"
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 f596333237d..ed33a94af6e 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
@@ -2,9 +2,15 @@
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import { __ } from '~/locale';
-import { DEFAULT, LOAD_FAILURE } from '../../constants';
+import { DEFAULT, DRAW_FAILURE, LOAD_FAILURE } from '../../constants';
import PipelineGraph from './graph_component.vue';
-import { unwrapPipelineData, toggleQueryPollingByVisibility, reportToSentry } from './utils';
+import {
+ getQueryHeaders,
+ reportToSentry,
+ serializeLoadErrors,
+ toggleQueryPollingByVisibility,
+ unwrapPipelineData,
+} from './utils';
export default {
name: 'PipelineGraphWrapper',
@@ -14,6 +20,12 @@ export default {
PipelineGraph,
},
inject: {
+ graphqlResourceEtag: {
+ default: '',
+ },
+ metricsPath: {
+ default: '',
+ },
pipelineIid: {
default: '',
},
@@ -29,11 +41,15 @@ export default {
};
},
errorTexts: {
+ [DRAW_FAILURE]: __('An error occurred while drawing job relationship links.'),
[LOAD_FAILURE]: __('We are currently unable to fetch data for this pipeline.'),
[DEFAULT]: __('An unknown error occurred while loading this graph.'),
},
apollo: {
pipeline: {
+ context() {
+ return getQueryHeaders(this.graphqlResourceEtag);
+ },
query: getPipelineDetails,
pollInterval: 10000,
variables() {
@@ -43,16 +59,41 @@ export default {
};
},
update(data) {
+ /*
+ This check prevents the pipeline from being overwritten
+ when a poll times out and the data returned is empty.
+ This can be removed once the timeout behavior is updated.
+ See: https://gitlab.com/gitlab-org/gitlab/-/issues/323213.
+ */
+
+ if (!data?.project?.pipeline) {
+ return this.pipeline;
+ }
+
return unwrapPipelineData(this.pipelineProjectPath, data);
},
- error() {
- this.reportFailure(LOAD_FAILURE);
+ error(err) {
+ this.reportFailure(LOAD_FAILURE, serializeLoadErrors(err));
+ },
+ result({ error }) {
+ /*
+ If there is a successful load after a failure, clear
+ the failure notification to avoid confusion.
+ */
+ if (!error && this.alertType === LOAD_FAILURE) {
+ this.hideAlert();
+ }
},
},
},
computed: {
alert() {
switch (this.alertType) {
+ case DRAW_FAILURE:
+ return {
+ text: this.$options.errorTexts[DRAW_FAILURE],
+ variant: 'danger',
+ };
case LOAD_FAILURE:
return {
text: this.$options.errorTexts[LOAD_FAILURE],
@@ -65,6 +106,12 @@ export default {
};
}
},
+ configPaths() {
+ return {
+ graphqlResourceEtag: this.graphqlResourceEtag,
+ metricsPath: this.metricsPath,
+ };
+ },
showLoadingIcon() {
/*
Shows the icon only when the graph is empty, not when it is is
@@ -82,14 +129,15 @@ export default {
methods: {
hideAlert() {
this.showAlert = false;
+ this.alertType = null;
},
refreshPipelineGraph() {
this.$apollo.queries.pipeline.refetch();
},
- reportFailure(type) {
+ reportFailure(type, err = '') {
this.showAlert = true;
- this.failureType = type;
- reportToSentry(this.$options.name, this.failureType);
+ this.alertType = type;
+ reportToSentry(this.$options.name, `type: ${this.alertType}, info: ${err}`);
},
},
};
@@ -102,6 +150,7 @@ export default {
<gl-loading-icon v-if="showLoadingIcon" class="gl-mx-auto gl-my-4" size="lg" />
<pipeline-graph
v-if="pipeline"
+ :config-paths="configPaths"
:pipeline="pipeline"
@error="reportFailure"
@refreshPipelineGraph="refreshPipelineGraph"
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 3ce77a1c60a..939400eb1c3 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
@@ -3,7 +3,14 @@ import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.qu
import { LOAD_FAILURE } from '../../constants';
import { ONE_COL_WIDTH, UPSTREAM } from './constants';
import LinkedPipeline from './linked_pipeline.vue';
-import { unwrapPipelineData, toggleQueryPollingByVisibility, reportToSentry } from './utils';
+import {
+ getQueryHeaders,
+ reportToSentry,
+ serializeLoadErrors,
+ toggleQueryPollingByVisibility,
+ unwrapPipelineData,
+ validateConfigPaths,
+} from './utils';
export default {
components: {
@@ -15,6 +22,11 @@ export default {
type: String,
required: true,
},
+ configPaths: {
+ type: Object,
+ required: true,
+ validator: validateConfigPaths,
+ },
linkedPipelines: {
type: Array,
required: true,
@@ -72,6 +84,9 @@ export default {
this.$apollo.addSmartQuery('currentPipeline', {
query: getPipelineDetails,
pollInterval: 10000,
+ context() {
+ return getQueryHeaders(this.configPaths.graphqlResourceEtag);
+ },
variables() {
return {
projectPath,
@@ -79,6 +94,17 @@ export default {
};
},
update(data) {
+ /*
+ This check prevents the pipeline from being overwritten
+ when a poll times out and the data returned is empty.
+ This can be removed once the timeout behavior is updated.
+ See: https://gitlab.com/gitlab-org/gitlab/-/issues/323213.
+ */
+
+ if (!data?.project?.pipeline) {
+ return this.currentPipeline;
+ }
+
return unwrapPipelineData(projectPath, data);
},
result() {
@@ -90,7 +116,9 @@ export default {
reportToSentry(
'linked_pipelines_column',
- `error type: ${LOAD_FAILURE}, error: ${err}, apollo error type: ${type}`,
+ `error type: ${LOAD_FAILURE}, error: ${serializeLoadErrors(
+ err,
+ )}, apollo error type: ${type}`,
);
},
});
@@ -175,6 +203,7 @@ export default {
v-if="isExpanded(pipeline.id)"
:type="type"
class="d-inline-block gl-mt-n2"
+ :config-paths="configPaths"
:pipeline="currentPipeline"
:is-linked-pipeline="true"
/>
diff --git a/app/assets/javascripts/pipelines/components/graph/utils.js b/app/assets/javascripts/pipelines/components/graph/utils.js
index 1a935599bfa..b9a8e2638bc 100644
--- a/app/assets/javascripts/pipelines/components/graph/utils.js
+++ b/app/assets/javascripts/pipelines/components/graph/utils.js
@@ -1,6 +1,6 @@
+import * as Sentry from '@sentry/browser';
import Visibility from 'visibilityjs';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import * as Sentry from '~/sentry/wrapper';
import { unwrapStagesWithNeeds } from '../unwrapping_utils';
const addMulti = (mainPipelineProjectPath, linkedPipeline) => {
@@ -10,6 +10,73 @@ const addMulti = (mainPipelineProjectPath, linkedPipeline) => {
};
};
+/* eslint-disable @gitlab/require-i18n-strings */
+const getQueryHeaders = (etagResource) => {
+ return {
+ fetchOptions: {
+ method: 'GET',
+ },
+ headers: {
+ 'X-GITLAB-GRAPHQL-FEATURE-CORRELATION': 'verify/ci/pipeline-graph',
+ 'X-GITLAB-GRAPHQL-RESOURCE-ETAG': etagResource,
+ 'X-Requested-With': 'XMLHttpRequest',
+ },
+ };
+};
+
+const reportToSentry = (component, failureType) => {
+ Sentry.withScope((scope) => {
+ scope.setTag('component', component);
+ Sentry.captureException(failureType);
+ });
+};
+
+const serializeGqlErr = (gqlError) => {
+ const { locations = [], message = '', path = [] } = gqlError;
+
+ return `
+ ${message}.
+ Locations: ${locations
+ .flatMap((loc) => Object.entries(loc))
+ .flat(2)
+ .join(' ')}.
+ Path: ${path.join(', ')}.
+ `;
+};
+
+const serializeLoadErrors = (errors) => {
+ const { gqlError, graphQLErrors, networkError, message } = errors;
+
+ if (graphQLErrors) {
+ return graphQLErrors.map((err) => serializeGqlErr(err)).join('; ');
+ }
+
+ if (gqlError) {
+ return serializeGqlErr(gqlError);
+ }
+
+ if (networkError) {
+ return `Network error: ${networkError.message}`;
+ }
+
+ return message;
+};
+
+/* eslint-enable @gitlab/require-i18n-strings */
+
+const toggleQueryPollingByVisibility = (queryRef, interval = 10000) => {
+ const stopStartQuery = (query) => {
+ if (!Visibility.hidden()) {
+ query.startPolling(interval);
+ } else {
+ query.stopPolling();
+ }
+ };
+
+ stopStartQuery(queryRef);
+ Visibility.change(stopStartQuery.bind(null, queryRef));
+};
+
const transformId = (linkedPipeline) => {
return { ...linkedPipeline, id: getIdFromGraphQLId(linkedPipeline.id) };
};
@@ -42,24 +109,14 @@ const unwrapPipelineData = (mainPipelineProjectPath, data) => {
};
};
-const toggleQueryPollingByVisibility = (queryRef, interval = 10000) => {
- const stopStartQuery = (query) => {
- if (!Visibility.hidden()) {
- query.startPolling(interval);
- } else {
- query.stopPolling();
- }
- };
-
- stopStartQuery(queryRef);
- Visibility.change(stopStartQuery.bind(null, queryRef));
-};
-
-export { unwrapPipelineData, toggleQueryPollingByVisibility };
+const validateConfigPaths = (value) => value.graphqlResourceEtag?.length > 0;
-export const reportToSentry = (component, failureType) => {
- Sentry.withScope((scope) => {
- scope.setTag('component', component);
- Sentry.captureException(failureType);
- });
+export {
+ getQueryHeaders,
+ reportToSentry,
+ serializeGqlErr,
+ serializeLoadErrors,
+ toggleQueryPollingByVisibility,
+ unwrapPipelineData,
+ validateConfigPaths,
};
diff --git a/app/assets/javascripts/pipelines/components/graph_shared/api.js b/app/assets/javascripts/pipelines/components/graph_shared/api.js
new file mode 100644
index 00000000000..04ac15ae24c
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/graph_shared/api.js
@@ -0,0 +1,8 @@
+import axios from '~/lib/utils/axios_utils';
+import { reportToSentry } from '../graph/utils';
+
+export const reportPerformance = (path, stats) => {
+ axios.post(path, stats).catch((err) => {
+ reportToSentry('links_inner_perf', `error: ${err}`);
+ });
+};
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 289e04e02c5..84ca0bf1443 100644
--- a/app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue
+++ b/app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue
@@ -1,8 +1,19 @@
<script>
import { isEmpty } from 'lodash';
+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 { DRAW_FAILURE } from '../../constants';
import { createJobsHash, generateJobNeedsDict } from '../../utils';
+import { reportToSentry } from '../graph/utils';
import { parseData } from '../parsing_utils';
+import { reportPerformance } from './api';
import { generateLinksData } from './drawing_utils';
export default {
@@ -25,6 +36,15 @@ export default {
type: Array,
required: true,
},
+ totalGroups: {
+ type: Number,
+ required: true,
+ },
+ metricsConfig: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
defaultLinkColor: {
type: String,
required: false,
@@ -43,6 +63,9 @@ export default {
};
},
computed: {
+ shouldCollectMetrics() {
+ return this.metricsConfig.collectMetrics && this.metricsConfig.path;
+ },
hasHighlightedJob() {
return Boolean(this.highlightedJob);
},
@@ -87,23 +110,70 @@ export default {
this.$emit('highlightedJobsChange', jobs);
},
},
+ errorCaptured(err, _vm, info) {
+ reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
+ },
mounted() {
if (!isEmpty(this.pipelineData)) {
this.prepareLinkData();
}
},
methods: {
+ beginPerfMeasure() {
+ if (this.shouldCollectMetrics) {
+ performanceMarkAndMeasure({ mark: PIPELINES_DETAIL_LINKS_MARK_CALCULATE_START });
+ }
+ },
+ finishPerfMeasureAndSend() {
+ 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 },
+ { name: PIPELINES_DETAIL_LINKS_TOTAL, value: this.links.length },
+ {
+ name: PIPELINES_DETAIL_LINKS_JOB_RATIO,
+ value: this.links.length / this.totalGroups,
+ },
+ ],
+ };
+
+ reportPerformance(this.metricsConfig.path, data);
+ });
+ },
isLinkHighlighted(linkRef) {
return this.highlightedLinks.includes(linkRef);
},
prepareLinkData() {
+ this.beginPerfMeasure();
try {
const arrayOfJobs = this.pipelineData.flatMap(({ groups }) => groups);
const parsedData = parseData(arrayOfJobs);
this.links = generateLinksData(parsedData, this.containerId, `-${this.pipelineId}`);
- } catch {
+ } catch (err) {
this.$emit('error', DRAW_FAILURE);
+ reportToSentry(this.$options.name, err);
}
+ this.finishPerfMeasureAndSend();
},
getLinkClasses(link) {
return [
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 1c1bc7ecb2a..5db204712df 100644
--- a/app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue
+++ b/app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue
@@ -1,6 +1,7 @@
<script>
import { GlAlert } from '@gitlab/ui';
import { __ } from '~/locale';
+import { reportToSentry } from '../graph/utils';
import LinksInner from './links_inner.vue';
export default {
@@ -42,7 +43,7 @@ export default {
}, 0);
},
showAlert() {
- return !this.showLinkedLayers && !this.alertDismissed;
+ return !this.containerZero && !this.showLinkedLayers && !this.alertDismissed;
},
showLinkedLayers() {
return (
@@ -50,6 +51,9 @@ export default {
);
},
},
+ errorCaptured(err, _vm, info) {
+ reportToSentry(this.$options.name, `error: ${err}, info: ${info}`);
+ },
methods: {
dismissAlert() {
this.alertDismissed = true;
@@ -66,6 +70,7 @@ export default {
v-if="showLinkedLayers"
:container-measurements="containerMeasurements"
:pipeline-data="pipelineData"
+ :total-groups="numGroups"
v-bind="$attrs"
v-on="$listeners"
>
diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue
index 4a7ee3b2af7..707d6966e77 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue
@@ -64,13 +64,6 @@ export default {
hasHighlightedJob() {
return Boolean(this.highlightedJob);
},
- alert() {
- if (this.hasError) {
- return this.failure;
- }
-
- return this.warning;
- },
failure() {
switch (this.failureType) {
case DRAW_FAILURE:
@@ -210,11 +203,11 @@ export default {
<div>
<gl-alert
v-if="hasError"
- :variant="alert.variant"
- :dismissible="alert.dismissible"
- @dismiss="alert.dismissible ? resetFailure : null"
+ :variant="failure.variant"
+ :dismissible="failure.dismissible"
+ @dismiss="resetFailure"
>
- {{ alert.text }}
+ {{ failure.text }}
</gl-alert>
<div
v-if="!hideGraph"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
index 8a656bb47f4..90c6acc9e6f 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
@@ -1,5 +1,6 @@
<script>
import { GlButton } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
import { s__ } from '~/locale';
export default {
@@ -14,10 +15,6 @@ export default {
GlButton,
},
props: {
- helpPagePath: {
- type: String,
- required: true,
- },
emptyStateSvgPath: {
type: String,
required: true,
@@ -27,6 +24,11 @@ export default {
required: true,
},
},
+ computed: {
+ ciHelpPagePath() {
+ return helpPagePath('ci/quick_start/index.md');
+ },
+ },
};
</script>
<template>
@@ -47,7 +49,7 @@ export default {
<div class="gl-text-center">
<gl-button
- :href="helpPagePath"
+ :href="ciHelpPagePath"
variant="info"
category="primary"
data-testid="get-started-pipelines"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue
new file mode 100644
index 00000000000..05372010d0f
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue
@@ -0,0 +1,54 @@
+<script>
+import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue';
+/**
+ * Renders the pipeline mini graph.
+ */
+export default {
+ components: {
+ PipelineStage,
+ },
+ props: {
+ stages: {
+ type: Array,
+ required: true,
+ },
+ updateDropdown: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ stagesClass: {
+ type: [Array, Object, String],
+ required: false,
+ default: '',
+ },
+ isMergeTrain: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ methods: {
+ onPipelineActionRequestComplete() {
+ this.$emit('pipelineActionRequestComplete');
+ },
+ },
+};
+</script>
+<template>
+ <div data-testid="widget-mini-pipeline-graph">
+ <div
+ v-for="stage in stages"
+ :key="stage.name"
+ :class="stagesClass"
+ class="stage-container dropdown"
+ >
+ <pipeline-stage
+ :stage="stage"
+ :update-dropdown="updateDropdown"
+ :is-merge-train="isMergeTrain"
+ @pipelineActionRequestComplete="onPipelineActionRequestComplete"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
new file mode 100644
index 00000000000..81eeead2171
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue
@@ -0,0 +1,119 @@
+<script>
+import { GlButton, GlTooltipDirective, GlModalDirective } from '@gitlab/ui';
+import { __ } from '~/locale';
+import eventHub from '../../event_hub';
+import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
+import PipelinesManualActions from './pipelines_manual_actions.vue';
+
+export default {
+ i18n: {
+ cancelTitle: __('Cancel'),
+ redeployTitle: __('Retry'),
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ GlModalDirective,
+ },
+ components: {
+ GlButton,
+ PipelinesManualActions,
+ PipelinesArtifactsComponent,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ cancelingPipeline: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ isRetrying: false,
+ };
+ },
+ computed: {
+ displayPipelineActions() {
+ return (
+ this.pipeline.flags.retryable ||
+ this.pipeline.flags.cancelable ||
+ this.pipeline.details.manual_actions.length ||
+ this.pipeline.details.artifacts.length
+ );
+ },
+ actions() {
+ if (!this.pipeline || !this.pipeline.details) {
+ return [];
+ }
+ const { details } = this.pipeline;
+ return [...(details.manual_actions || []), ...(details.scheduled_actions || [])];
+ },
+ isCancelling() {
+ return this.cancelingPipeline === this.pipeline.id;
+ },
+ },
+ watch: {
+ pipeline() {
+ this.isRetrying = false;
+ },
+ },
+ methods: {
+ handleCancelClick() {
+ eventHub.$emit('openConfirmationModal', {
+ pipeline: this.pipeline,
+ endpoint: this.pipeline.cancel_path,
+ });
+ },
+ handleRetryClick() {
+ this.isRetrying = true;
+ eventHub.$emit('retryPipeline', this.pipeline.retry_path);
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="displayPipelineActions" class="gl-text-right">
+ <div class="btn-group">
+ <pipelines-manual-actions v-if="actions.length > 0" :actions="actions" />
+
+ <pipelines-artifacts-component
+ v-if="pipeline.details.artifacts.length"
+ :artifacts="pipeline.details.artifacts"
+ />
+
+ <gl-button
+ v-if="pipeline.flags.retryable"
+ v-gl-tooltip.hover
+ :aria-label="$options.i18n.redeployTitle"
+ :title="$options.i18n.redeployTitle"
+ :disabled="isRetrying"
+ :loading="isRetrying"
+ class="js-pipelines-retry-button"
+ data-qa-selector="pipeline_retry_button"
+ icon="repeat"
+ variant="default"
+ category="secondary"
+ @click="handleRetryClick"
+ />
+
+ <gl-button
+ v-if="pipeline.flags.cancelable"
+ v-gl-tooltip.hover
+ v-gl-modal-directive="'confirmation-modal'"
+ :aria-label="$options.i18n.cancelTitle"
+ :title="$options.i18n.cancelTitle"
+ :loading="isCancelling"
+ :disabled="isCancelling"
+ icon="close"
+ variant="danger"
+ category="primary"
+ class="js-pipelines-cancel-button"
+ @click="handleCancelClick"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue
new file mode 100644
index 00000000000..50e60418f66
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue
@@ -0,0 +1,152 @@
+<script>
+/**
+ * Renders each stage of the pipeline mini graph.
+ *
+ * Given the provided endpoint will make a request to
+ * fetch the dropdown data when the stage is clicked.
+ *
+ * Request is made inside this component to make it reusable between:
+ * 1. Pipelines main table
+ * 2. Pipelines table in commit and Merge request views
+ * 3. Merge request widget
+ * 4. Commit widget
+ */
+
+import { GlDropdown, GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import { deprecatedCreateFlash as Flash } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import eventHub from '../../event_hub';
+import JobItem from '../graph/job_item.vue';
+
+export default {
+ components: {
+ GlIcon,
+ GlLoadingIcon,
+ GlDropdown,
+ JobItem,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ stage: {
+ type: Object,
+ required: true,
+ },
+ updateDropdown: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isMergeTrain: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ isLoading: false,
+ dropdownContent: [],
+ };
+ },
+ computed: {
+ triggerButtonClass() {
+ return `ci-status-icon-${this.stage.status.group}`;
+ },
+ borderlessIcon() {
+ return `${this.stage.status.icon}_borderless`;
+ },
+ },
+ watch: {
+ updateDropdown() {
+ if (this.updateDropdown && this.isDropdownOpen() && !this.isLoading) {
+ this.fetchJobs();
+ }
+ },
+ },
+ methods: {
+ onShowDropdown() {
+ eventHub.$emit('clickedDropdown');
+ this.isLoading = true;
+ this.fetchJobs();
+ },
+ fetchJobs() {
+ axios
+ .get(this.stage.dropdown_path)
+ .then(({ data }) => {
+ this.dropdownContent = data.latest_statuses;
+ this.isLoading = false;
+ })
+ .catch(() => {
+ this.$refs.stageGlDropdown.hide();
+ this.isLoading = false;
+
+ Flash(__('Something went wrong on our end.'));
+ });
+ },
+ isDropdownOpen() {
+ return this.$el.classList.contains('show');
+ },
+ pipelineActionRequestComplete() {
+ // close the dropdown in MR widget
+ this.$refs.stageGlDropdown.hide();
+
+ // warn the pipelines table to update
+ this.$emit('pipelineActionRequestComplete');
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-dropdown
+ ref="stageGlDropdown"
+ v-gl-tooltip.hover
+ data-testid="mini-pipeline-graph-dropdown"
+ :title="stage.title"
+ variant="link"
+ :lazy="true"
+ :popper-opts="{ placement: 'bottom' }"
+ :toggle-class="['mini-pipeline-graph-dropdown-toggle', triggerButtonClass]"
+ menu-class="mini-pipeline-graph-dropdown-menu"
+ @show="onShowDropdown"
+ >
+ <template #button-content>
+ <span class="gl-pointer-events-none">
+ <gl-icon :name="borderlessIcon" />
+ </span>
+ </template>
+ <gl-loading-icon v-if="isLoading" />
+ <ul
+ v-else
+ class="js-builds-dropdown-list scrollable-menu"
+ data-testid="mini-pipeline-graph-dropdown-menu-list"
+ >
+ <li v-for="job in dropdownContent" :key="job.id">
+ <job-item
+ :dropdown-length="dropdownContent.length"
+ :job="job"
+ css-class-job-name="mini-pipeline-graph-dropdown-item"
+ @pipelineActionRequestComplete="pipelineActionRequestComplete"
+ />
+ </li>
+ <template v-if="isMergeTrain">
+ <li class="gl-new-dropdown-divider" role="presentation">
+ <hr role="separator" aria-orientation="horizontal" class="dropdown-divider" />
+ </li>
+ <li>
+ <div
+ class="gl-display-flex gl-align-items-center"
+ data-testid="warning-message-merge-trains"
+ >
+ <div class="menu-item gl-font-sm gl-text-gray-300!">
+ {{ s__('Pipeline|Merge train pipeline jobs can not be retried') }}
+ </div>
+ </div>
+ </li>
+ </template>
+ </ul>
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
index 6ac60727f23..c707b395192 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
@@ -1,10 +1,12 @@
<script>
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
UserAvatarLink,
},
+ mixins: [glFeatureFlagMixin()],
props: {
pipeline: {
type: Object,
@@ -15,11 +17,19 @@ export default {
user() {
return this.pipeline.user;
},
+ classes() {
+ const triggererClass = 'pipeline-triggerer';
+
+ if (this.glFeatures.newPipelinesTable) {
+ return triggererClass;
+ }
+ return `table-section section-10 d-none d-md-block ${triggererClass}`;
+ },
},
};
</script>
<template>
- <div class="table-section section-10 d-none d-md-block pipeline-triggerer">
+ <div :class="classes" data-testid="pipeline-triggerer">
<user-avatar-link
v-if="user"
:link-href="user.path"
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 823ada133d2..0de520a2ca7 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
@@ -1,5 +1,7 @@
<script>
import { GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { SCHEDULE_ORIGIN } from '../../constants';
export default {
@@ -12,6 +14,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ mixins: [glFeatureFlagMixin()],
inject: {
targetProjectFullPath: {
default: '',
@@ -26,10 +29,6 @@ export default {
type: String,
required: true,
},
- autoDevopsHelpPath: {
- type: String,
- required: true,
- },
},
computed: {
user() {
@@ -44,11 +43,25 @@ export default {
this.pipeline?.project?.full_path !== `/${this.targetProjectFullPath}`,
);
},
+ autoDevopsTagId() {
+ return `pipeline-url-autodevops-${this.pipeline.id}`;
+ },
+ autoDevopsHelpPath() {
+ return helpPagePath('topics/autodevops/index.md');
+ },
+ classes() {
+ const tagsClass = 'pipeline-tags';
+
+ if (this.glFeatures.newPipelinesTable) {
+ return tagsClass;
+ }
+ return `table-section section-10 d-none d-md-block ${tagsClass}`;
+ },
},
};
</script>
<template>
- <div class="table-section section-10 d-none d-md-block pipeline-tags">
+ <div :class="classes" data-testid="pipeline-url-table-cell">
<gl-link
:href="pipeline.path"
data-testid="pipeline-url-link"
@@ -103,38 +116,43 @@ export default {
data-testid="pipeline-url-failure"
>{{ __('error') }}</gl-badge
>
- <gl-link
- v-if="pipeline.flags.auto_devops"
- :id="`pipeline-url-autodevops-${pipeline.id}`"
- tabindex="0"
- data-testid="pipeline-url-autodevops"
- role="button"
- ><gl-badge variant="info" size="sm">{{ __('Auto DevOps') }}</gl-badge></gl-link
- >
- <gl-popover
- :target="`pipeline-url-autodevops-${pipeline.id}`"
- triggers="focus"
- placement="top"
- >
- <template #title>
- <div class="gl-font-weight-normal gl-line-height-normal">
- <gl-sprintf
- :message="
- __(
- 'This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}',
- )
- "
- >
- <template #strong="{ content }">
- <b>{{ content }}</b>
- </template>
- </gl-sprintf>
- </div>
- </template>
- <gl-link :href="autoDevopsHelpPath" target="_blank" rel="noopener noreferrer nofollow">{{
- __('Learn more about Auto DevOps')
- }}</gl-link>
- </gl-popover>
+ <template v-if="pipeline.flags.auto_devops">
+ <gl-link
+ :id="autoDevopsTagId"
+ tabindex="0"
+ data-testid="pipeline-url-autodevops"
+ role="button"
+ >
+ <gl-badge variant="info" size="sm">
+ {{ __('Auto DevOps') }}
+ </gl-badge>
+ </gl-link>
+ <gl-popover :target="autoDevopsTagId" triggers="focus" placement="top">
+ <template #title>
+ <div class="gl-font-weight-normal gl-line-height-normal">
+ <gl-sprintf
+ :message="
+ __(
+ 'This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}',
+ )
+ "
+ >
+ <template #strong="{ content }">
+ <b>{{ content }}</b>
+ </template>
+ </gl-sprintf>
+ </div>
+ </template>
+ <gl-link
+ :href="autoDevopsHelpPath"
+ data-testid="pipeline-url-autodevops-link"
+ target="_blank"
+ >
+ {{ __('Learn more about Auto DevOps') }}
+ </gl-link>
+ </gl-popover>
+ </template>
+
<gl-badge
v-if="pipeline.flags.stuck"
variant="warning"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
index 48009a9fcb8..19d93e7d083 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
@@ -52,10 +52,6 @@ export default {
required: false,
default: '',
},
- helpPagePath: {
- type: String,
- required: true,
- },
emptyStateSvgPath: {
type: String,
required: true,
@@ -68,10 +64,6 @@ export default {
type: String,
required: true,
},
- autoDevopsHelpPath: {
- type: String,
- required: true,
- },
hasGitlabCi: {
type: Boolean,
required: true,
@@ -337,7 +329,6 @@ export default {
<empty-state
v-else-if="stateToRender === $options.stateMap.emptyState"
- :help-page-path="helpPagePath"
:empty-state-svg-path="emptyStateSvgPath"
:can-set-ci="canCreatePipeline"
/>
@@ -362,7 +353,6 @@ export default {
:pipelines="state.pipelines"
:pipeline-schedule-url="pipelineScheduleUrl"
:update-graph-dropdown="updateGraphDropdown"
- :auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType"
/>
</div>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue
index b13460b4c68..9c3990f82df 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue
@@ -31,6 +31,8 @@ export default {
:text="$options.translations.artifacts"
:aria-label="$options.translations.artifacts"
icon="download"
+ right
+ lazy
text-sr-only
>
<gl-dropdown-item
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_commit.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_commit.vue
new file mode 100644
index 00000000000..cc676883c1d
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_commit.vue
@@ -0,0 +1,85 @@
+<script>
+import { CHILD_VIEW } from '~/pipelines/constants';
+import CommitComponent from '~/vue_shared/components/commit.vue';
+
+export default {
+ components: {
+ CommitComponent,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ viewType: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ commitAuthor() {
+ let commitAuthorInformation;
+
+ if (!this.pipeline || !this.pipeline.commit) {
+ return null;
+ }
+
+ // 1. person who is an author of a commit might be a GitLab user
+ if (this.pipeline.commit.author) {
+ // 2. if person who is an author of a commit is a GitLab user
+ // they can have a GitLab avatar
+ if (this.pipeline.commit.author.avatar_url) {
+ commitAuthorInformation = this.pipeline.commit.author;
+
+ // 3. If GitLab user does not have avatar, they might have a Gravatar
+ } else if (this.pipeline.commit.author_gravatar_url) {
+ commitAuthorInformation = {
+ ...this.pipeline.commit.author,
+ avatar_url: this.pipeline.commit.author_gravatar_url,
+ };
+ }
+ // 4. If committer is not a GitLab User, they can have a Gravatar
+ } else {
+ commitAuthorInformation = {
+ avatar_url: this.pipeline.commit.author_gravatar_url,
+ path: `mailto:${this.pipeline.commit.author_email}`,
+ username: this.pipeline.commit.author_name,
+ };
+ }
+
+ return commitAuthorInformation;
+ },
+ commitTag() {
+ return this.pipeline?.ref?.tag;
+ },
+ commitRef() {
+ return this.pipeline?.ref;
+ },
+ commitUrl() {
+ return this.pipeline?.commit?.commit_path;
+ },
+ commitShortSha() {
+ return this.pipeline?.commit?.short_id;
+ },
+ commitTitle() {
+ return this.pipeline?.commit?.title;
+ },
+ isChildView() {
+ return this.viewType === CHILD_VIEW;
+ },
+ },
+};
+</script>
+
+<template>
+ <commit-component
+ :tag="commitTag"
+ :commit-ref="commitRef"
+ :commit-url="commitUrl"
+ :merge-request-ref="pipeline.merge_request"
+ :short-sha="commitShortSha"
+ :title="commitTitle"
+ :author="commitAuthor"
+ :show-ref-info="!isChildView"
+ />
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_manual_actions.vue
index 6890cbb9bed..b94f1a42039 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_manual_actions.vue
@@ -82,6 +82,7 @@ export default {
:loading="isLoading"
data-testid="pipelines-manual-actions-dropdown"
right
+ lazy
icon="play"
>
<gl-dropdown-item
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue
new file mode 100644
index 00000000000..cc3c8d522b3
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue
@@ -0,0 +1,37 @@
+<script>
+import { CHILD_VIEW } from '~/pipelines/constants';
+import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
+
+export default {
+ components: {
+ CiBadge,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ viewType: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ pipelineStatus() {
+ return this.pipeline?.details?.status ?? {};
+ },
+ isChildView() {
+ return this.viewType === CHILD_VIEW;
+ },
+ },
+};
+</script>
+
+<template>
+ <ci-badge
+ :status="pipelineStatus"
+ :show-text="!isChildView"
+ :icon-classes="'gl-vertical-align-middle!'"
+ data-qa-selector="pipeline_commit_status"
+ />
+</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 24c67184e56..aa27aa7e50d 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -1,22 +1,97 @@
<script>
-import { GlTooltipDirective } from '@gitlab/ui';
+import { GlTable, GlTooltipDirective } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../../event_hub';
+import PipelineMiniGraph from './pipeline_mini_graph.vue';
+import PipelineOperations from './pipeline_operations.vue';
import PipelineStopModal from './pipeline_stop_modal.vue';
+import PipelineTriggerer from './pipeline_triggerer.vue';
+import PipelineUrl from './pipeline_url.vue';
+import PipelinesCommit from './pipelines_commit.vue';
+import PipelinesStatusBadge from './pipelines_status_badge.vue';
import PipelinesTableRowComponent from './pipelines_table_row.vue';
+import PipelinesTimeago from './time_ago.vue';
+
+const DEFAULT_TD_CLASS = 'gl-p-5!';
+const HIDE_TD_ON_MOBILE = 'gl-display-none! gl-lg-display-table-cell!';
+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!';
-/**
- * Pipelines Table Component.
- *
- * Given an array of objects, renders a table.
- */
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: {
- PipelinesTableRowComponent,
+ GlTable,
+ PipelinesCommit,
+ PipelineMiniGraph,
+ PipelineOperations,
+ PipelinesStatusBadge,
PipelineStopModal,
+ PipelinesTableRowComponent,
+ PipelinesTimeago,
+ PipelineTriggerer,
+ PipelineUrl,
},
directives: {
GlTooltip: GlTooltipDirective,
},
+ mixins: [glFeatureFlagMixin()],
props: {
pipelines: {
type: Array,
@@ -32,10 +107,6 @@ export default {
required: false,
default: false,
},
- autoDevopsHelpPath: {
- type: String,
- required: true,
- },
viewType: {
type: String,
required: true,
@@ -70,42 +141,103 @@ export default {
eventHub.$emit('postAction', this.endpoint);
this.cancelingPipeline = this.pipelineId;
},
+ onPipelineActionRequestComplete() {
+ eventHub.$emit('refreshPipelinesTable');
+ },
},
};
</script>
<template>
<div class="ci-table">
- <div class="gl-responsive-table-row table-row-header" role="row">
- <div class="table-section section-10 js-pipeline-status" role="rowheader">
- {{ s__('Pipeline|Status') }}
- </div>
- <div class="table-section section-10 js-pipeline-info pipeline-info" role="rowheader">
- {{ s__('Pipeline|Pipeline') }}
- </div>
- <div class="table-section section-10 js-triggerer-info triggerer-info" role="rowheader">
- {{ s__('Pipeline|Triggerer') }}
- </div>
- <div class="table-section section-20 js-pipeline-commit pipeline-commit" role="rowheader">
- {{ s__('Pipeline|Commit') }}
- </div>
- <div class="table-section section-15 js-pipeline-stages pipeline-stages" role="rowheader">
- {{ s__('Pipeline|Stages') }}
- </div>
- <div class="table-section section-15" role="rowheader"></div>
- <div class="table-section section-20" role="rowheader">
- <slot name="table-header-actions"></slot>
+ <div v-if="!glFeatures.newPipelinesTable" data-testid="legacy-ci-table">
+ <div class="gl-responsive-table-row table-row-header" role="row">
+ <div class="table-section section-10 js-pipeline-status" role="rowheader">
+ {{ s__('Pipeline|Status') }}
+ </div>
+ <div class="table-section section-10 js-pipeline-info pipeline-info" role="rowheader">
+ {{ s__('Pipeline|Pipeline') }}
+ </div>
+ <div class="table-section section-10 js-triggerer-info triggerer-info" role="rowheader">
+ {{ s__('Pipeline|Triggerer') }}
+ </div>
+ <div class="table-section section-20 js-pipeline-commit pipeline-commit" role="rowheader">
+ {{ s__('Pipeline|Commit') }}
+ </div>
+ <div class="table-section section-15 js-pipeline-stages pipeline-stages" role="rowheader">
+ {{ s__('Pipeline|Stages') }}
+ </div>
+ <div class="table-section section-15" role="rowheader"></div>
+ <div class="table-section section-20" role="rowheader">
+ <slot name="table-header-actions"></slot>
+ </div>
</div>
+ <pipelines-table-row-component
+ v-for="model in pipelines"
+ :key="model.id"
+ :pipeline="model"
+ :pipeline-schedule-url="pipelineScheduleUrl"
+ :update-graph-dropdown="updateGraphDropdown"
+ :view-type="viewType"
+ :canceling-pipeline="cancelingPipeline"
+ />
</div>
- <pipelines-table-row-component
- v-for="model in pipelines"
- :key="model.id"
- :pipeline="model"
- :pipeline-schedule-url="pipelineScheduleUrl"
- :update-graph-dropdown="updateGraphDropdown"
- :auto-devops-help-path="autoDevopsHelpPath"
- :view-type="viewType"
- :canceling-pipeline="cancelingPipeline"
- />
+
+ <gl-table
+ v-else
+ :fields="$options.fields"
+ :items="pipelines"
+ tbody-tr-class="commit"
+ :tbody-tr-attr="{ 'data-testid': 'pipeline-table-row' }"
+ stacked="lg"
+ fixed
+ >
+ <template #head(actions)>
+ <span class="gl-display-block gl-lg-display-none!">{{ s__('Pipeline|Actions') }}</span>
+ <slot name="table-header-actions"></slot>
+ </template>
+
+ <template #table-colgroup="{ fields }">
+ <col v-for="field in fields" :key="field.key" :class="field.columnClass" />
+ </template>
+
+ <template #cell(status)="{ item }">
+ <pipelines-status-badge :pipeline="item" :view-type="viewType" />
+ </template>
+
+ <template #cell(pipeline)="{ item }">
+ <pipeline-url :pipeline="item" :pipeline-schedule-url="pipelineScheduleUrl" />
+ </template>
+
+ <template #cell(triggerer)="{ item }">
+ <pipeline-triggerer :pipeline="item" />
+ </template>
+
+ <template #cell(commit)="{ item }">
+ <pipelines-commit :pipeline="item" :view-type="viewType" />
+ </template>
+
+ <template #cell(stages)="{ item }">
+ <div class="stage-cell">
+ <!-- This empty div should be removed, see https://gitlab.com/gitlab-org/gitlab/-/issues/323488 -->
+ <div></div>
+ <pipeline-mini-graph
+ v-if="item.details && item.details.stages && item.details.stages.length > 0"
+ :stages="item.details.stages"
+ :update-dropdown="updateGraphDropdown"
+ @pipelineActionRequestComplete="onPipelineActionRequestComplete"
+ />
+ </div>
+ </template>
+
+ <template #cell(timeago)="{ item }">
+ <pipelines-timeago :pipeline="item" />
+ </template>
+
+ <template #cell(actions)="{ item }">
+ <pipeline-operations :pipeline="item" :canceling-pipeline="cancelingPipeline" />
+ </template>
+ </gl-table>
+
<pipeline-stop-modal :pipeline="pipeline" @submit="onSubmit" />
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
index 572abe2a24a..f684a0b0fcd 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
@@ -3,13 +3,12 @@ import { GlButton, GlTooltipDirective, GlModalDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
import CommitComponent from '~/vue_shared/components/commit.vue';
-import { PIPELINES_TABLE } from '../../constants';
import eventHub from '../../event_hub';
+import PipelineMiniGraph from './pipeline_mini_graph.vue';
import PipelineTriggerer from './pipeline_triggerer.vue';
import PipelineUrl from './pipeline_url.vue';
-import PipelinesActionsComponent from './pipelines_actions.vue';
import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
-import PipelineStage from './stage.vue';
+import PipelinesManualActionsComponent from './pipelines_manual_actions.vue';
import PipelinesTimeago from './time_ago.vue';
export default {
@@ -22,10 +21,10 @@ export default {
GlModalDirective,
},
components: {
- PipelinesActionsComponent,
+ PipelinesManualActionsComponent,
PipelinesArtifactsComponent,
CommitComponent,
- PipelineStage,
+ PipelineMiniGraph,
PipelineUrl,
PipelineTriggerer,
CiBadge,
@@ -47,10 +46,6 @@ export default {
required: false,
default: false,
},
- autoDevopsHelpPath: {
- type: String,
- required: true,
- },
viewType: {
type: String,
required: true,
@@ -61,7 +56,6 @@ export default {
default: null,
},
},
- pipelinesTable: PIPELINES_TABLE,
data() {
return {
isRetrying: false,
@@ -137,15 +131,12 @@ export default {
commitTitle() {
return this.pipeline?.commit?.title;
},
- pipelineDuration() {
- return this.pipeline?.details?.duration ?? 0;
- },
- pipelineFinishedAt() {
- return this.pipeline?.details?.finished_at ?? '';
- },
pipelineStatus() {
return this.pipeline?.details?.status ?? {};
},
+ hasStages() {
+ return this.pipeline?.details?.stages?.length > 0;
+ },
displayPipelineActions() {
return (
this.pipeline.flags.retryable ||
@@ -177,6 +168,10 @@ export default {
this.isRetrying = true;
eventHub.$emit('retryPipeline', this.pipeline.retry_path);
},
+ handlePipelineActionRequestComplete() {
+ // warn the pipelines table to update
+ eventHub.$emit('refreshPipelinesTable');
+ },
},
};
</script>
@@ -194,11 +189,7 @@ export default {
</div>
</div>
- <pipeline-url
- :pipeline="pipeline"
- :pipeline-schedule-url="pipelineScheduleUrl"
- :auto-devops-help-path="autoDevopsHelpPath"
- />
+ <pipeline-url :pipeline="pipeline" :pipeline-schedule-url="pipelineScheduleUrl" />
<pipeline-triggerer :pipeline="pipeline" />
<div class="table-section section-wrap section-20">
@@ -220,35 +211,23 @@ export default {
<div class="table-section section-wrap section-15 stage-cell">
<div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Stages') }}</div>
<div class="table-mobile-content">
- <template v-if="pipeline.details.stages.length > 0">
- <div
- v-for="(stage, index) in pipeline.details.stages"
- :key="index"
- class="stage-container dropdown"
- data-testid="widget-mini-pipeline-graph"
- >
- <pipeline-stage
- :type="$options.pipelinesTable"
- :stage="stage"
- :update-dropdown="updateGraphDropdown"
- />
- </div>
- </template>
+ <pipeline-mini-graph
+ v-if="hasStages"
+ :stages="pipeline.details.stages"
+ :update-dropdown="updateGraphDropdown"
+ @pipelineActionRequestComplete="handlePipelineActionRequestComplete"
+ />
</div>
</div>
- <pipelines-timeago
- class="gl-text-right"
- :duration="pipelineDuration"
- :finished-time="pipelineFinishedAt"
- />
+ <pipelines-timeago class="gl-text-right" :pipeline="pipeline" />
<div
v-if="displayPipelineActions"
class="table-section section-20 table-button-footer pipeline-actions"
>
<div class="btn-group table-action-buttons">
- <pipelines-actions-component v-if="actions.length > 0" :actions="actions" />
+ <pipelines-manual-actions-component v-if="actions.length > 0" :actions="actions" />
<pipelines-artifacts-component
v-if="pipeline.details.artifacts.length"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
deleted file mode 100644
index f5dfb9e72d5..00000000000
--- a/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
+++ /dev/null
@@ -1,234 +0,0 @@
-<script>
-/**
- * Renders each stage of the pipeline mini graph.
- *
- * Given the provided endpoint will make a request to
- * fetch the dropdown data when the stage is clicked.
- *
- * Request is made inside this component to make it reusable between:
- * 1. Pipelines main table
- * 2. Pipelines table in commit and Merge request views
- * 3. Merge request widget
- * 4. Commit widget
- */
-import { GlDropdown, GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui';
-import $ from 'jquery';
-import { deprecatedCreateFlash as Flash } from '~/flash';
-import axios from '~/lib/utils/axios_utils';
-import { __ } from '~/locale';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { PIPELINES_TABLE } from '../../constants';
-import eventHub from '../../event_hub';
-import JobItem from '../graph/job_item.vue';
-
-export default {
- components: {
- GlIcon,
- GlLoadingIcon,
- GlDropdown,
- JobItem,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- mixins: [glFeatureFlagsMixin()],
- props: {
- stage: {
- type: Object,
- required: true,
- },
-
- updateDropdown: {
- type: Boolean,
- required: false,
- default: false,
- },
-
- type: {
- type: String,
- required: false,
- default: '',
- },
- },
- data() {
- return {
- isLoading: false,
- dropdownContent: [],
- };
- },
- computed: {
- isCiMiniPipelineGlDropdown() {
- // Feature flag ci_mini_pipeline_gl_dropdown
- // See more at https://gitlab.com/gitlab-org/gitlab/-/issues/300400
- return this.glFeatures?.ciMiniPipelineGlDropdown;
- },
- triggerButtonClass() {
- return `ci-status-icon-${this.stage.status.group}`;
- },
- borderlessIcon() {
- return `${this.stage.status.icon}_borderless`;
- },
- },
- watch: {
- updateDropdown() {
- if (this.updateDropdown && this.isDropdownOpen() && !this.isLoading) {
- this.fetchJobs();
- }
- },
- },
- updated() {
- if (!this.isCiMiniPipelineGlDropdown && this.dropdownContent.length) {
- this.stopDropdownClickPropagation();
- }
- },
- methods: {
- onShowDropdown() {
- eventHub.$emit('clickedDropdown');
- this.isLoading = true;
- this.fetchJobs();
- },
- onClickStage() {
- if (!this.isDropdownOpen()) {
- eventHub.$emit('clickedDropdown');
- this.isLoading = true;
- this.fetchJobs();
- }
- },
- fetchJobs() {
- axios
- .get(this.stage.dropdown_path)
- .then(({ data }) => {
- this.dropdownContent = data.latest_statuses;
- this.isLoading = false;
- })
- .catch(() => {
- if (this.isCiMiniPipelineGlDropdown) {
- this.$refs.stageGlDropdown.hide();
- } else {
- this.closeDropdown();
- }
- this.isLoading = false;
-
- Flash(__('Something went wrong on our end.'));
- });
- },
- /**
- * When the user right clicks or cmd/ctrl + click in the job name
- * the dropdown should not be closed and the link should open in another tab,
- * so we stop propagation of the click event inside the dropdown.
- *
- * Since this component is rendered multiple times per page we need to guarantee we only
- * target the click event of this component.
- *
- * Note: This should be removed once ci_mini_pipeline_gl_dropdown FF is removed as true.
- */
- stopDropdownClickPropagation() {
- $(
- '.js-builds-dropdown-list button, .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item',
- this.$el,
- ).on('click', (e) => {
- e.stopPropagation();
- });
- },
- closeDropdown() {
- if (this.isDropdownOpen()) {
- $(this.$refs.dropdown).dropdown('toggle');
- }
- },
- isDropdownOpen() {
- return this.$el.classList.contains('show');
- },
- pipelineActionRequestComplete() {
- if (this.type === PIPELINES_TABLE) {
- // warn the table to update
- eventHub.$emit('refreshPipelinesTable');
- return;
- }
- // close the dropdown in mr widget
- if (this.isCiMiniPipelineGlDropdown) {
- this.$refs.stageGlDropdown.hide();
- } else {
- $(this.$refs.dropdown).dropdown('toggle');
- }
- },
- },
-};
-</script>
-
-<template>
- <div class="dropdown">
- <gl-dropdown
- v-if="isCiMiniPipelineGlDropdown"
- ref="stageGlDropdown"
- v-gl-tooltip.hover
- data-testid="mini-pipeline-graph-dropdown"
- :title="stage.title"
- variant="link"
- :lazy="true"
- :popper-opts="{ placement: 'bottom' }"
- :toggle-class="['mini-pipeline-graph-gl-dropdown-toggle', triggerButtonClass]"
- menu-class="mini-pipeline-graph-dropdown-menu"
- @show="onShowDropdown"
- >
- <template #button-content>
- <span class="gl-pointer-events-none">
- <gl-icon :name="borderlessIcon" />
- </span>
- </template>
- <gl-loading-icon v-if="isLoading" />
- <ul
- v-else
- class="js-builds-dropdown-list scrollable-menu"
- data-testid="mini-pipeline-graph-dropdown-menu-list"
- >
- <li v-for="job in dropdownContent" :key="job.id">
- <job-item
- :dropdown-length="dropdownContent.length"
- :job="job"
- css-class-job-name="mini-pipeline-graph-dropdown-item"
- @pipelineActionRequestComplete="pipelineActionRequestComplete"
- />
- </li>
- </ul>
- </gl-dropdown>
-
- <template v-else>
- <button
- id="stageDropdown"
- ref="dropdown"
- v-gl-tooltip.hover
- :class="triggerButtonClass"
- :title="stage.title"
- class="mini-pipeline-graph-dropdown-toggle"
- data-testid="mini-pipeline-graph-dropdown-toggle"
- data-toggle="dropdown"
- data-display="static"
- type="button"
- aria-haspopup="true"
- aria-expanded="false"
- @click="onClickStage"
- >
- <span :aria-label="stage.title" aria-hidden="true" class="gl-pointer-events-none">
- <gl-icon :name="borderlessIcon" />
- </span>
- </button>
-
- <div
- class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"
- aria-labelledby="stageDropdown"
- >
- <gl-loading-icon v-if="isLoading" />
- <ul v-else class="js-builds-dropdown-list scrollable-menu">
- <li v-for="job in dropdownContent" :key="job.id">
- <job-item
- :dropdown-length="dropdownContent.length"
- :job="job"
- css-class-job-name="mini-pipeline-graph-dropdown-item"
- @pipelineActionRequestComplete="pipelineActionRequestComplete"
- />
- </li>
- </ul>
- </div>
- </template>
- </div>
-</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
index 5548a1021f5..278089b6155 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
@@ -1,5 +1,6 @@
<script>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import timeagoMixin from '~/vue_shared/mixins/timeago';
export default {
@@ -7,23 +8,19 @@ export default {
GlTooltip: GlTooltipDirective,
},
components: { GlIcon },
- mixins: [timeagoMixin],
+ mixins: [timeagoMixin, glFeatureFlagMixin()],
props: {
- finishedTime: {
- type: String,
- required: true,
- },
- duration: {
- type: Number,
+ pipeline: {
+ type: Object,
required: true,
},
},
computed: {
- hasDuration() {
- return this.duration > 0;
+ duration() {
+ return this.pipeline?.details?.duration;
},
- hasFinishedTime() {
- return this.finishedTime !== '';
+ finishedTime() {
+ return this.pipeline?.details?.finished_at;
},
durationFormatted() {
const date = new Date(this.duration * 1000);
@@ -45,20 +42,28 @@ export default {
return `${hh}:${mm}:${ss}`;
},
+ legacySectionClass() {
+ return !this.glFeatures.newPipelinesTable ? 'table-section section-15' : '';
+ },
+ legacyTableMobileClass() {
+ return !this.glFeatures.newPipelinesTable ? 'table-mobile-content' : '';
+ },
},
};
</script>
<template>
- <div class="table-section section-15">
- <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Duration') }}</div>
- <div class="table-mobile-content">
- <p v-if="hasDuration" class="duration">
- <gl-icon name="timer" class="gl-vertical-align-baseline!" />
+ <div :class="legacySectionClass">
+ <div v-if="!glFeatures.newPipelinesTable" class="table-mobile-header" role="rowheader">
+ {{ s__('Pipeline|Duration') }}
+ </div>
+ <div :class="legacyTableMobileClass">
+ <p v-if="duration" class="duration">
+ <gl-icon name="timer" class="gl-vertical-align-baseline!" :size="12" />
{{ durationFormatted }}
</p>
- <p v-if="hasFinishedTime" class="finished-at d-none d-md-block">
- <gl-icon name="calendar" class="gl-vertical-align-baseline!" />
+ <p v-if="finishedTime" class="finished-at d-none d-md-block">
+ <gl-icon name="calendar" class="gl-vertical-align-baseline!" :size="12" />
<time
v-gl-tooltip
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index 757d285ef19..21b114825a6 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -1,7 +1,6 @@
import { s__, __ } from '~/locale';
export const CANCEL_REQUEST = 'CANCEL_REQUEST';
-export const PIPELINES_TABLE = 'PIPELINES_TABLE';
export const LAYOUT_CHANGE_DELAY = 300;
export const FILTER_PIPELINES_SEARCH_DELAY = 200;
export const ANY_TRIGGER_AUTHOR = 'Any';
@@ -34,3 +33,5 @@ export const LOAD_FAILURE = 'load_failure';
export const PARSE_FAILURE = 'parse_failure';
export const POST_FAILURE = 'post_failure';
export const UNSUPPORTED_DATA = 'unsupported_data';
+
+export const CHILD_VIEW = 'child';
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index f837851e5c1..c3444f38ea0 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -93,8 +93,7 @@ export default async function initPipelineDetailsBundle() {
/* webpackChunkName: 'createPipelinesDetailApp' */ './pipeline_details_graph'
);
- const { pipelineProjectPath, pipelineIid } = dataset;
- createPipelinesDetailApp(SELECTORS.PIPELINE_GRAPH, pipelineProjectPath, pipelineIid);
+ createPipelinesDetailApp(SELECTORS.PIPELINE_GRAPH, dataset);
} catch {
Flash(__('An error occurred while loading the pipeline.'));
}
diff --git a/app/assets/javascripts/pipelines/pipeline_details_graph.js b/app/assets/javascripts/pipelines/pipeline_details_graph.js
index 55f3731a3ca..9eba39738dc 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_graph.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_graph.js
@@ -11,12 +11,15 @@ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
- batchMax: 2,
+ useGet: true,
},
),
});
-const createPipelinesDetailApp = (selector, pipelineProjectPath, pipelineIid) => {
+const createPipelinesDetailApp = (
+ selector,
+ { pipelineProjectPath, pipelineIid, metricsPath, graphqlResourceEtag } = {},
+) => {
// eslint-disable-next-line no-new
new Vue({
el: selector,
@@ -25,8 +28,10 @@ const createPipelinesDetailApp = (selector, pipelineProjectPath, pipelineIid) =>
},
apolloProvider,
provide: {
+ metricsPath,
pipelineProjectPath,
pipelineIid,
+ graphqlResourceEtag,
dataMethod: GRAPHQL,
},
errorCaptured(err, _vm, info) {
diff --git a/app/assets/javascripts/pipelines/pipelines_index.js b/app/assets/javascripts/pipelines/pipelines_index.js
index 7bcc51e18e5..0e2e9785956 100644
--- a/app/assets/javascripts/pipelines/pipelines_index.js
+++ b/app/assets/javascripts/pipelines/pipelines_index.js
@@ -23,11 +23,9 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
const {
endpoint,
pipelineScheduleUrl,
- helpPagePath,
emptyStateSvgPath,
errorStateSvgPath,
noPipelinesSvgPath,
- autoDevopsHelpPath,
newPipelinePath,
canCreatePipeline,
hasGitlabCi,
@@ -56,11 +54,9 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
store: this.store,
endpoint,
pipelineScheduleUrl,
- helpPagePath,
emptyStateSvgPath,
errorStateSvgPath,
noPipelinesSvgPath,
- autoDevopsHelpPath,
newPipelinePath,
canCreatePipeline: parseBoolean(canCreatePipeline),
hasGitlabCi: parseBoolean(hasGitlabCi),