diff options
author | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 22:34:23 +0300 |
---|---|---|
committer | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 22:34:23 +0300 |
commit | 6438df3a1e0fb944485cebf07976160184697d72 (patch) | |
tree | 00b09bfd170e77ae9391b1a2f5a93ef6839f2597 /app/assets/javascripts/pipelines/components/pipeline_graph | |
parent | 42bcd54d971da7ef2854b896a7b34f4ef8601067 (diff) |
Add latest changes from gitlab-org/gitlab@13-8-stable-eev13.8.0-rc42
Diffstat (limited to 'app/assets/javascripts/pipelines/components/pipeline_graph')
-rw-r--r-- | app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js | 96 | ||||
-rw-r--r-- | app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue | 114 |
2 files changed, 62 insertions, 148 deletions
diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js b/app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js deleted file mode 100644 index 35230e1511b..00000000000 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js +++ /dev/null @@ -1,96 +0,0 @@ -import * as d3 from 'd3'; -import { createUniqueLinkId } from '../../utils'; -/** - * This function expects its first argument data structure - * to be the same shaped as the one generated by `parseData`, - * which contains nodes and links. For each link, - * we find the nodes in the graph, calculate their coordinates and - * trace the lines that represent the needs of each job. - * @param {Object} nodeDict - Resulting object of `parseData` with nodes and links - * @param {Object} jobs - An object where each key is the job name that contains the job data - * @param {ref} svg - Reference to the svg we draw in - * @returns {Array} Links that contain all the information about them - */ - -export const generateLinksData = ({ links }, containerID) => { - const containerEl = document.getElementById(containerID); - return links.map(link => { - const path = d3.path(); - - const sourceId = link.source; - const targetId = link.target; - - const sourceNodeEl = document.getElementById(sourceId); - const targetNodeEl = document.getElementById(targetId); - - const sourceNodeCoordinates = sourceNodeEl.getBoundingClientRect(); - const targetNodeCoordinates = targetNodeEl.getBoundingClientRect(); - const containerCoordinates = containerEl.getBoundingClientRect(); - - // Because we add the svg dynamically and calculate the coordinates - // with plain JS and not D3, we need to account for the fact that - // the coordinates we are getting are absolutes, but we want to draw - // relative to the svg container, which starts at `containerCoordinates(x,y)` - // so we substract these from the total. We also need to remove the padding - // from the total to make sure it's aligned properly. We then make the line - // positioned in the center of the job node by adding half the height - // of the job pill. - const paddingLeft = Number( - window - .getComputedStyle(containerEl, null) - .getPropertyValue('padding-left') - .replace('px', ''), - ); - const paddingTop = Number( - window - .getComputedStyle(containerEl, null) - .getPropertyValue('padding-top') - .replace('px', ''), - ); - - const sourceNodeX = sourceNodeCoordinates.right - containerCoordinates.x - paddingLeft; - const sourceNodeY = - sourceNodeCoordinates.top - - containerCoordinates.y - - paddingTop + - sourceNodeCoordinates.height / 2; - const targetNodeX = targetNodeCoordinates.x - containerCoordinates.x - paddingLeft; - const targetNodeY = - targetNodeCoordinates.y - - containerCoordinates.y - - paddingTop + - sourceNodeCoordinates.height / 2; - - // Start point - path.moveTo(sourceNodeX, sourceNodeY); - - // Make cross-stages lines a straight line all the way - // until we can safely draw the bezier to look nice. - const straightLineDestinationX = targetNodeX - 100; - const controlPointX = straightLineDestinationX + (targetNodeX - straightLineDestinationX) / 2; - - if (straightLineDestinationX > 0) { - path.lineTo(straightLineDestinationX, sourceNodeY); - } - - // Add bezier curve. The first 4 coordinates are the 2 control - // points to create the curve, and the last one is the end point (x, y). - // We want our control points to be in the middle of the line - path.bezierCurveTo( - controlPointX, - sourceNodeY, - controlPointX, - targetNodeY, - targetNodeX, - targetNodeY, - ); - - return { - ...link, - source: sourceId, - target: targetId, - ref: createUniqueLinkId(sourceId, targetId), - path: path.toString(), - }; - }); -}; 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 73e5f2542fb..8636808b69e 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue @@ -1,12 +1,10 @@ <script> -import { isEmpty } from 'lodash'; import { GlAlert } from '@gitlab/ui'; import { __ } from '~/locale'; +import { generateLinksData } from '../graph_shared/drawing_utils'; import JobPill from './job_pill.vue'; import StagePill from './stage_pill.vue'; -import { generateLinksData } from './drawing_utils'; import { parseData } from '../parsing_utils'; -import { unwrapArrayOfJobs } from '../unwrapping_utils'; import { DRAW_FAILURE, DEFAULT, INVALID_CI_CONFIG, EMPTY_PIPELINE_DATA } from '../../constants'; import { createJobsHash, generateJobNeedsDict } from '../../utils'; import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants'; @@ -23,8 +21,6 @@ export default { errorTexts: { [DRAW_FAILURE]: __('Could not draw the lines for job relationships'), [DEFAULT]: __('An unknown error occurred.'), - }, - warningTexts: { [EMPTY_PIPELINE_DATA]: __( 'The visualization will appear in this tab when the CI/CD configuration file is populated with valid syntax.', ), @@ -47,21 +43,24 @@ export default { }; }, computed: { + hideGraph() { + // We won't even try to render the graph with these condition + // because it would cause additional errors down the line for the user + // which is confusing. + return this.isPipelineDataEmpty || this.isInvalidCiConfig; + }, + pipelineStages() { + return this.pipelineData?.stages || []; + }, isPipelineDataEmpty() { - return !this.isInvalidCiConfig && isEmpty(this.pipelineData?.stages); + return !this.isInvalidCiConfig && this.pipelineStages.length === 0; }, isInvalidCiConfig() { return this.pipelineData?.status === CI_CONFIG_STATUS_INVALID; }, - showAlert() { - return this.hasError || this.hasWarning; - }, hasError() { return this.failureType; }, - hasWarning() { - return this.warning; - }, hasHighlightedJob() { return Boolean(this.highlightedJob); }, @@ -73,26 +72,32 @@ export default { return this.warning; }, failure() { - const text = this.$options.errorTexts[this.failureType] || this.$options.errorTexts[DEFAULT]; - - return { text, variant: 'danger', dismissible: true }; - }, - warning() { - if (this.isPipelineDataEmpty) { - return { - text: this.$options.warningTexts[EMPTY_PIPELINE_DATA], - variant: 'tip', - dismissible: false, - }; - } else if (this.isInvalidCiConfig) { - return { - text: this.$options.warningTexts[INVALID_CI_CONFIG], - variant: 'danger', - dismissible: false, - }; + switch (this.failureType) { + case DRAW_FAILURE: + return { + text: this.$options.errorTexts[DRAW_FAILURE], + variant: 'danger', + dismissible: true, + }; + case EMPTY_PIPELINE_DATA: + return { + text: this.$options.errorTexts[EMPTY_PIPELINE_DATA], + variant: 'tip', + dismissible: false, + }; + case INVALID_CI_CONFIG: + return { + text: this.$options.errorTexts[INVALID_CI_CONFIG], + variant: 'danger', + dismissible: false, + }; + default: + return { + text: this.$options.errorTexts[DEFAULT], + variant: 'danger', + dismissible: true, + }; } - - return null; }, viewBox() { return [0, 0, this.width, this.height]; @@ -100,40 +105,45 @@ export default { highlightedJobs() { // If you are hovering on a job, then the jobs we want to highlight are: // The job you are currently hovering + all of its needs. - return this.hasHighlightedJob - ? [this.highlightedJob, ...this.needsObject[this.highlightedJob]] - : []; + return [this.highlightedJob, ...this.needsObject[this.highlightedJob]]; }, highlightedLinks() { // If you are hovering on a job, then the links we want to highlight are: // All the links whose `source` and `target` are highlighted jobs. if (this.hasHighlightedJob) { - const filteredLinks = this.links.filter(link => { + const filteredLinks = this.links.filter((link) => { return ( this.highlightedJobs.includes(link.source) && this.highlightedJobs.includes(link.target) ); }); - return filteredLinks.map(link => link.ref); + return filteredLinks.map((link) => link.ref); } return []; }, }, - mounted() { - if (!this.isPipelineDataEmpty && !this.isInvalidCiConfig) { - // This guarantee that all sub-elements are rendered - // https://v3.vuejs.org/api/options-lifecycle-hooks.html#mounted - this.$nextTick(() => { - this.getGraphDimensions(); - this.prepareLinkData(); - }); - } + watch: { + pipelineData: { + immediate: true, + handler() { + if (this.isPipelineDataEmpty) { + this.reportFailure(EMPTY_PIPELINE_DATA); + } else if (this.isInvalidCiConfig) { + this.reportFailure(INVALID_CI_CONFIG); + } else { + this.$nextTick(() => { + this.computeGraphDimensions(); + this.prepareLinkData(); + }); + } + }, + }, }, methods: { prepareLinkData() { try { - const arrayOfJobs = unwrapArrayOfJobs(this.pipelineData); + const arrayOfJobs = this.pipelineStages.flatMap(({ groups }) => groups); const parsedData = parseData(arrayOfJobs); this.links = generateLinksData(parsedData, this.$options.CONTAINER_ID); } catch { @@ -141,7 +151,7 @@ export default { } }, getStageBackgroundClasses(index) { - const { length } = this.pipelineData.stages; + const { length } = this.pipelineStages; // It's possible for a graph to have only one stage, in which // case we concatenate both the left and right rounding classes if (length === 1) { @@ -162,7 +172,7 @@ export default { // The first time we hover, we create the object where // we store all the data to properly highlight the needs. if (!this.needsObject) { - const jobs = createJobsHash(this.pipelineData); + const jobs = createJobsHash(this.pipelineStages); this.needsObject = generateJobNeedsDict(jobs) ?? {}; } @@ -171,7 +181,7 @@ export default { removeHighlightNeeds() { this.highlightedJob = null; }, - getGraphDimensions() { + computeGraphDimensions() { this.width = `${this.$refs[this.$options.CONTAINER_REF].scrollWidth}`; this.height = `${this.$refs[this.$options.CONTAINER_REF].scrollHeight}`; }, @@ -199,7 +209,7 @@ export default { <template> <div> <gl-alert - v-if="showAlert" + v-if="hasError" :variant="alert.variant" :dismissible="alert.dismissible" @dismiss="alert.dismissible ? resetFailure : null" @@ -207,7 +217,7 @@ export default { {{ alert.text }} </gl-alert> <div - v-if="!hasWarning" + v-if="!hideGraph" :id="$options.CONTAINER_ID" :ref="$options.CONTAINER_REF" class="gl-display-flex gl-bg-gray-50 gl-px-4 gl-overflow-auto gl-relative gl-py-7" @@ -227,7 +237,7 @@ export default { </template> </svg> <div - v-for="(stage, index) in pipelineData.stages" + v-for="(stage, index) in pipelineStages" :key="`${stage.name}-${index}`" class="gl-flex-direction-column" > |