diff options
Diffstat (limited to 'app/assets/javascripts/pipelines/components')
6 files changed, 285 insertions, 9 deletions
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index cfc72327ef7..e29509ce074 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -1,20 +1,120 @@ <script> +import _ from 'underscore'; import { GlLoadingIcon } from '@gitlab/ui'; import StageColumnComponent from './stage_column_component.vue'; import GraphMixin from '../../mixins/graph_component_mixin'; -import GraphWidthMixin from '~/pipelines/mixins/graph_width_mixin'; +import GraphWidthMixin from '../../mixins/graph_width_mixin'; +import LinkedPipelinesColumn from './linked_pipelines_column.vue'; +import GraphBundleMixin from '../../mixins/graph_pipeline_bundle_mixin'; export default { + name: 'PipelineGraph', components: { StageColumnComponent, GlLoadingIcon, + LinkedPipelinesColumn, + }, + mixins: [GraphMixin, GraphWidthMixin, 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 { + triggeredTopIndex: 1, + }; + }, + computed: { + hasTriggeredBy() { + return ( + this.type !== this.$options.downstream && + this.triggeredByPipelines && + this.pipeline.triggered_by !== null + ); + }, + triggeredByPipelines() { + return this.pipeline.triggered_by; + }, + hasTriggered() { + return ( + this.type !== this.$options.upstream && + this.triggeredPipelines && + this.pipeline.triggered.length > 0 + ); + }, + triggeredPipelines() { + return this.pipeline.triggered; + }, + expandedTriggeredBy() { + return ( + this.pipeline.triggered_by && + _.isArray(this.pipeline.triggered_by) && + this.pipeline.triggered_by.find(el => el.isExpanded) + ); + }, + expandedTriggered() { + return this.pipeline.triggered && this.pipeline.triggered.find(el => el.isExpanded); + }, + + /** + * Calculates the margin top of the clicked downstream pipeline by + * adding the height of each linked pipeline and the margin + */ + marginTop() { + return `${this.triggeredTopIndex * 52}px`; + }, + pipelineTypeUpstream() { + return this.type !== this.$options.downstream && this.expandedTriggeredBy; + }, + pipelineTypeDownstream() { + return this.type !== this.$options.upstream && this.expandedTriggered; + }, + }, + methods: { + handleClickedDownstream(pipeline, clickedIndex) { + this.triggeredTopIndex = clickedIndex; + this.$emit('onClickTriggered', this.pipeline, pipeline); + }, + hasOnlyOneJob(stage) { + return stage.groups.length === 1; + }, + hasDownstream(index, length) { + return index === length - 1 && this.hasTriggered; + }, + hasUpstream(index) { + return index === 0 && this.hasTriggeredBy; + }, }, - mixins: [GraphMixin, GraphWidthMixin], }; </script> <template> <div class="build-content middle-block js-pipeline-graph"> - <div class="pipeline-visualization pipeline-graph pipeline-tab-content"> + <div + class="pipeline-visualization pipeline-graph" + :class="{ 'pipeline-tab-content': !isLinkedPipeline }" + > <div :style="{ paddingLeft: `${graphLeftPadding}px`, @@ -23,21 +123,80 @@ export default { > <gl-loading-icon v-if="isLoading" class="m-auto" :size="3" /> - <ul v-if="!isLoading" class="stage-column-list"> + <pipeline-graph + v-if="pipelineTypeUpstream" + type="upstream" + class="d-inline-block upstream-pipeline" + :class="`js-upstream-pipeline-${expandedTriggeredBy.id}`" + :is-loading="false" + :pipeline="expandedTriggeredBy" + :is-linked-pipeline="true" + :mediator="mediator" + @onClickTriggeredBy=" + (parentPipeline, pipeline) => clickTriggeredByPipeline(parentPipeline, pipeline) + " + @refreshPipelineGraph="requestRefreshPipelineGraph" + /> + + <linked-pipelines-column + v-if="hasTriggeredBy" + :linked-pipelines="triggeredByPipelines" + :column-title="__('Upstream')" + graph-position="left" + @linkedPipelineClick=" + linkedPipeline => $emit('onClickTriggeredBy', pipeline, linkedPipeline) + " + /> + + <ul + v-if="!isLoading" + :class="{ + 'inline js-has-linked-pipelines': hasTriggered || hasTriggeredBy, + }" + class="stage-column-list align-top" + > <stage-column-component v-for="(stage, index) in graph" :key="stage.name" :class="{ - 'append-right-48': shouldAddRightMargin(index), + 'has-upstream prepend-left-64': hasUpstream(index), + 'has-downstream': hasDownstream(index, graph.length), + 'has-only-one-job': hasOnlyOneJob(stage), + 'append-right-46': shouldAddRightMargin(index), }" :title="capitalizeStageName(stage.name)" :groups="stage.groups" :stage-connector-class="stageConnectorClass(index, stage)" :is-first-column="isFirstColumn(index)" + :has-triggered-by="hasTriggeredBy" :action="stage.status.action" @refreshPipelineGraph="refreshPipelineGraph" /> </ul> + + <linked-pipelines-column + v-if="hasTriggered" + :linked-pipelines="triggeredPipelines" + :column-title="__('Downstream')" + graph-position="right" + @linkedPipelineClick="handleClickedDownstream" + /> + + <pipeline-graph + v-if="pipelineTypeDownstream" + type="downstream" + class="d-inline-block" + :class="`js-downstream-pipeline-${expandedTriggered.id}`" + :is-loading="false" + :pipeline="expandedTriggered" + :is-linked-pipeline="true" + :style="{ 'margin-top': marginTop }" + :mediator="mediator" + @onClickTriggered=" + (parentPipeline, pipeline) => clickTriggeredPipeline(parentPipeline, pipeline) + " + @refreshPipelineGraph="requestRefreshPipelineGraph" + /> </div> </div> </div> diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue new file mode 100644 index 00000000000..4e7d77863b9 --- /dev/null +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue @@ -0,0 +1,65 @@ +<script> +import { GlLoadingIcon, GlTooltipDirective, GlButton } from '@gitlab/ui'; +import CiStatus from '~/vue_shared/components/ci_icon.vue'; + +export default { + directives: { + GlTooltip: GlTooltipDirective, + }, + components: { + CiStatus, + GlLoadingIcon, + GlButton, + }, + props: { + pipeline: { + type: Object, + required: true, + }, + }, + computed: { + tooltipText() { + return `${this.projectName} - ${this.pipelineStatus.label}`; + }, + buttonId() { + return `js-linked-pipeline-${this.pipeline.id}`; + }, + pipelineStatus() { + return this.pipeline.details.status; + }, + projectName() { + return this.pipeline.project.name; + }, + }, + methods: { + onClickLinkedPipeline() { + this.$root.$emit('bv::hide::tooltip', this.buttonId); + this.$emit('pipelineClicked'); + }, + }, +}; +</script> + +<template> + <li class="linked-pipeline build"> + <div class="curve"></div> + <gl-button + :id="buttonId" + v-gl-tooltip + :title="tooltipText" + class="js-linked-pipeline-content linked-pipeline-content" + data-qa-selector="linked_pipeline_button" + :class="`js-pipeline-expand-${pipeline.id}`" + @click="onClickLinkedPipeline" + > + <gl-loading-icon v-if="pipeline.isLoading" class="js-linked-pipeline-loading d-inline" /> + <ci-status + v-else + :status="pipelineStatus" + css-classes="position-top-0" + class="js-linked-pipeline-status" + /> + <span class="str-truncated align-bottom"> {{ projectName }} • #{{ pipeline.id }} </span> + </gl-button> + </li> +</template> diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue new file mode 100644 index 00000000000..6efdde2b17e --- /dev/null +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue @@ -0,0 +1,52 @@ +<script> +import LinkedPipeline from './linked_pipeline.vue'; + +export default { + components: { + LinkedPipeline, + }, + props: { + columnTitle: { + type: String, + required: true, + }, + linkedPipelines: { + type: Array, + required: true, + }, + graphPosition: { + type: String, + required: true, + }, + }, + computed: { + columnClass() { + const positionValues = { + right: 'prepend-left-64', + left: 'append-right-32', + }; + return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`; + }, + }, +}; +</script> + +<template> + <div :class="columnClass" class="stage-column linked-pipelines-column"> + <div class="stage-name linked-pipelines-column-title">{{ columnTitle }}</div> + <div class="cross-project-triangle"></div> + <ul> + <linked-pipeline + v-for="(pipeline, index) in linkedPipelines" + :key="pipeline.id" + :class="{ + 'flat-connector-before': index === 0 && graphPosition === 'right', + active: pipeline.isExpanded, + 'left-connector': pipeline.isExpanded && graphPosition === 'left', + }" + :pipeline="pipeline" + @pipelineClicked="$emit('linkedPipelineClick', pipeline, index)" + /> + </ul> + </div> +</template> diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue index d5c124dc0ca..db7714808fd 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -1,6 +1,6 @@ <script> import _ from 'underscore'; -import stageColumnMixin from 'ee_else_ce/pipelines/mixins/stage_column_mixin'; +import stageColumnMixin from '../../mixins/stage_column_mixin'; import JobItem from './job_item.vue'; import JobGroupDropdown from './job_group_dropdown.vue'; import ActionComponent from './action_component.vue'; diff --git a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue b/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue index 4cafd147511..2e71b3c637b 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue @@ -1,6 +1,6 @@ <script> import _ from 'underscore'; -import GlModal from '~/vue_shared/components/gl_modal.vue'; +import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; import { GlLink } from '@gitlab/ui'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; @@ -13,7 +13,7 @@ import { s__, sprintf } from '~/locale'; */ export default { components: { - GlModal, + GlModal: DeprecatedModal2, GlLink, ClipboardButton, CiIcon, diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue index a08f732dda7..30c830d78f9 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue @@ -104,7 +104,7 @@ export default { v-gl-tooltip :title=" __( - 'Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more on the documentation for Pipelines for Merged Results.', + 'Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.', ) " class="js-pipeline-url-detached badge badge-info" |