diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 18:18:33 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 18:18:33 +0000 |
commit | f64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch) | |
tree | a2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /app/assets/javascripts/pipelines/components/graph | |
parent | bfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff) | |
download | gitlab-ce-f64a639bcfa1fc2bc89ca7db268f594306edfd7c.tar.gz |
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'app/assets/javascripts/pipelines/components/graph')
4 files changed, 194 insertions, 38 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..363226a0d85 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 ( @@ -95,8 +106,8 @@ export default { height: this.$refs[this.containerId].scrollHeight, }; }, - onError(errorType) { - this.$emit('error', errorType); + onError(payload) { + this.$emit('error', payload); }, setJob(jobName) { this.hoveredJobName = jobName; @@ -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,8 @@ export default { :container-id="containerId" :container-measurements="measurements" :highlighted-job="hoveredJobName" + :metrics-config="metricsConfig" + :never-show-links="true" default-link-color="gl-stroke-transparent" @error="onError" @highlightedJobsChange="updateHighlightedJobs" @@ -170,6 +184,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..962f2ca2a4c 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,45 @@ 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({ type: LOAD_FAILURE, skipSentry: true }); + reportToSentry( + this.$options.name, + `type: ${LOAD_FAILURE}, info: ${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 +110,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,15 +133,20 @@ export default { methods: { hideAlert() { this.showAlert = false; + this.alertType = null; }, refreshPipelineGraph() { this.$apollo.queries.pipeline.refetch(); }, - reportFailure(type) { + /* eslint-disable @gitlab/require-i18n-strings */ + reportFailure({ type, err = 'No error string passed.', skipSentry = false }) { this.showAlert = true; - this.failureType = type; - reportToSentry(this.$options.name, this.failureType); + this.alertType = type; + if (!skipSentry) { + reportToSentry(this.$options.name, `type: ${type}, info: ${err}`); + } }, + /* eslint-enable @gitlab/require-i18n-strings */ }, }; </script> @@ -102,6 +158,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..b55a77a3c4f 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,18 +94,29 @@ 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() { this.loadingPipelineId = null; this.$emit('scrollContainer'); }, - error(err, _vm, _key, type) { - this.$emit('error', LOAD_FAILURE); + error(err) { + this.$emit('error', { type: LOAD_FAILURE, skipSentry: true }); reportToSentry( 'linked_pipelines_column', - `error type: ${LOAD_FAILURE}, error: ${err}, apollo error type: ${type}`, + `error type: ${LOAD_FAILURE}, error: ${serializeLoadErrors(err)}`, ); }, }); @@ -175,6 +201,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, }; |