diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-20 21:42:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-20 21:42:06 +0300 |
commit | 6e4e1050d9dba2b7b2523fdd1768823ab85feef4 (patch) | |
tree | 78be5963ec075d80116a932011d695dd33910b4e /app/assets/javascripts/pipelines | |
parent | 1ce776de4ae122aba3f349c02c17cebeaa8ecf07 (diff) |
Add latest changes from gitlab-org/gitlab@13-3-stable-ee
Diffstat (limited to 'app/assets/javascripts/pipelines')
32 files changed, 314 insertions, 310 deletions
diff --git a/app/assets/javascripts/pipelines/components/dag/dag.vue b/app/assets/javascripts/pipelines/components/dag/dag.vue index 85163a666e2..8487da3d621 100644 --- a/app/assets/javascripts/pipelines/components/dag/dag.vue +++ b/app/assets/javascripts/pipelines/components/dag/dag.vue @@ -1,8 +1,9 @@ <script> -import { GlAlert, GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; +import { GlAlert, GlButton, GlEmptyState, GlSprintf } from '@gitlab/ui'; import { isEmpty } from 'lodash'; -import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; +import { fetchPolicies } from '~/lib/graphql'; +import getDagVisData from '../../graphql/queries/get_dag_vis_data.query.graphql'; import DagGraph from './dag_graph.vue'; import DagAnnotations from './dag_annotations.vue'; import { @@ -23,35 +24,68 @@ export default { DagAnnotations, DagGraph, GlAlert, - GlLink, GlSprintf, GlEmptyState, GlButton, }, - props: { - graphUrl: { - type: String, - required: false, - default: '', + inject: { + dagDocPath: { + default: null, }, emptySvgPath: { - type: String, - required: true, default: '', }, - dagDocPath: { - type: String, - required: true, + pipelineIid: { + default: '', + }, + pipelineProjectPath: { default: '', }, }, + apollo: { + graphData: { + fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, + query: getDagVisData, + variables() { + return { + projectPath: this.pipelineProjectPath, + iid: this.pipelineIid, + }; + }, + update(data) { + const { + stages: { nodes: stages }, + } = data.project.pipeline; + + const unwrappedGroups = stages + .map(({ name, groups: { nodes: groups } }) => { + return groups.map(group => { + return { category: name, ...group }; + }); + }) + .flat(2); + + const nodes = unwrappedGroups.map(group => { + const jobs = group.jobs.nodes.map(({ name, needs }) => { + return { name, needs: needs.nodes.map(need => need.name) }; + }); + + return { ...group, jobs }; + }); + + return nodes; + }, + error() { + this.reportFailure(LOAD_FAILURE); + }, + }, + }, data() { return { annotationsMap: {}, failureType: null, graphData: null, showFailureAlert: false, - showBetaInfo: true, hasNoDependentJobs: false, }; }, @@ -72,11 +106,6 @@ export default { button: __('Learn more about job dependencies'), }, computed: { - betaMessage() { - return __( - 'This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}.', - ); - }, failure() { switch (this.failureType) { case LOAD_FAILURE: @@ -97,32 +126,20 @@ export default { default: return { text: this.$options.errorTexts[DEFAULT], - vatiant: 'danger', + variant: 'danger', }; } }, + processedData() { + return this.processGraphData(this.graphData); + }, shouldDisplayAnnotations() { return !isEmpty(this.annotationsMap); }, shouldDisplayGraph() { - return Boolean(!this.showFailureAlert && this.graphData); + return Boolean(!this.showFailureAlert && !this.hasNoDependentJobs && this.graphData); }, }, - mounted() { - const { processGraphData, reportFailure } = this; - - if (!this.graphUrl) { - reportFailure(); - return; - } - - axios - .get(this.graphUrl) - .then(response => { - processGraphData(response.data); - }) - .catch(() => reportFailure(LOAD_FAILURE)); - }, methods: { addAnnotationToMap({ uid, source, target }) { this.$set(this.annotationsMap, uid, { source, target }); @@ -131,32 +148,29 @@ export default { let parsed; try { - parsed = parseData(data.stages); + parsed = parseData(data); } catch { this.reportFailure(PARSE_FAILURE); - return; + return {}; } if (parsed.links.length === 1) { this.reportFailure(UNSUPPORTED_DATA); - return; + return {}; } // If there are no links, we don't report failure // as it simply means the user does not use job dependencies if (parsed.links.length === 0) { this.hasNoDependentJobs = true; - return; + return {}; } - this.graphData = parsed; + return parsed; }, hideAlert() { this.showFailureAlert = false; }, - hideBetaInfo() { - this.showBetaInfo = false; - }, removeAnnotationFromMap({ uid }) { this.$delete(this.annotationsMap, uid); }, @@ -188,20 +202,11 @@ export default { {{ failure.text }} </gl-alert> - <gl-alert v-if="showBetaInfo" @dismiss="hideBetaInfo"> - <gl-sprintf :message="betaMessage"> - <template #link="{ content }"> - <gl-link href="https://gitlab.com/gitlab-org/gitlab/-/issues/220368" target="_blank"> - {{ content }} - </gl-link> - </template> - </gl-sprintf> - </gl-alert> <div class="gl-relative"> <dag-annotations v-if="shouldDisplayAnnotations" :annotations="annotationsMap" /> <dag-graph v-if="shouldDisplayGraph" - :graph-data="graphData" + :graph-data="processedData" @onFailure="reportFailure" @update-annotation="updateAnnotation" /> @@ -228,7 +233,7 @@ export default { </p> </div> </template> - <template #actions> + <template v-if="dagDocPath" #actions> <gl-button :href="dagDocPath" target="__blank" variant="success"> {{ $options.emptyStateTexts.button }} </gl-button> diff --git a/app/assets/javascripts/pipelines/components/dag/parsing_utils.js b/app/assets/javascripts/pipelines/components/dag/parsing_utils.js index 3234f80ee91..1ed415688f2 100644 --- a/app/assets/javascripts/pipelines/components/dag/parsing_utils.js +++ b/app/assets/javascripts/pipelines/components/dag/parsing_utils.js @@ -5,14 +5,16 @@ import { uniqWith, isEqual } from 'lodash'; received from the endpoint into the format the d3 graph expects. Input is of the form: - [stages] - stages: {name, groups} - groups: [{ name, size, jobs }] - name is a group name; in the case that the group has one job, it is - also the job name - size is the number of parallel jobs - jobs: [{ name, needs}] - job name is either the same as the group name or group x/y + [nodes] + nodes: [{category, name, jobs, size}] + category is the stage name + name is a group name; in the case that the group has one job, it is + also the job name + size is the number of parallel jobs + jobs: [{ name, needs}] + job name is either the same as the group name or group x/y + needs: [job-names] + needs is an array of job-name strings Output is of the form: { nodes: [node], links: [link] } @@ -20,30 +22,17 @@ import { uniqWith, isEqual } from 'lodash'; link: { source, target, value }, with source & target being node names and value being a constant - We create nodes, create links, and then dedupe the links, so that in the case where + We create nodes in the GraphQL update function, and then here we create the node dictionary, + then create links, and then dedupe the links, so that in the case where job 4 depends on job 1 and job 2, and job 2 depends on job 1, we show only a single link from job 1 to job 2 then another from job 2 to job 4. - CREATE NODES - stage.name -> node.category - stage.group.name -> node.name (this is the group name if there are parallel jobs) - stage.group.jobs -> node.jobs - stage.group.size -> node.size - CREATE LINKS - stages.groups.name -> target - stages.groups.needs.each -> source (source is the name of the group, not the parallel job) + nodes.name -> target + nodes.name.needs.each -> source (source is the name of the group, not the parallel job) 10 -> value (constant) */ -export const createNodes = data => { - return data.flatMap(({ groups, name }) => { - return groups.map(group => { - return { ...group, category: name }; - }); - }); -}; - export const createNodeDict = nodes => { return nodes.reduce((acc, node) => { const newNode = { @@ -62,13 +51,6 @@ export const createNodeDict = nodes => { }, {}); }; -export const createNodesStructure = data => { - const nodes = createNodes(data); - const nodeDict = createNodeDict(nodes); - - return { nodes, nodeDict }; -}; - export const makeLinksFromNodes = (nodes, nodeDict) => { const constantLinkValue = 10; // all links are the same weight return nodes @@ -126,8 +108,8 @@ export const filterByAncestors = (links, nodeDict) => return !allAncestors.includes(source); }); -export const parseData = data => { - const { nodes, nodeDict } = createNodesStructure(data); +export const parseData = nodes => { + const nodeDict = createNodeDict(nodes); const allLinks = makeLinksFromNodes(nodes, nodeDict); const filteredLinks = filterByAncestors(allLinks, nodeDict); const links = uniqWith(filteredLinks, isEqual); diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue index c5e95036f4f..137455bcae1 100644 --- a/app/assets/javascripts/pipelines/components/graph/action_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue @@ -1,9 +1,9 @@ <script> -import { GlTooltipDirective, GlDeprecatedButton, GlLoadingIcon } from '@gitlab/ui'; +import { GlTooltipDirective, GlButton, GlLoadingIcon } from '@gitlab/ui'; import axios from '~/lib/utils/axios_utils'; import { dasherize } from '~/lib/utils/text_utility'; import { __ } from '~/locale'; -import createFlash from '~/flash'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; import Icon from '~/vue_shared/components/icon.vue'; /** @@ -19,7 +19,7 @@ import Icon from '~/vue_shared/components/icon.vue'; export default { components: { Icon, - GlDeprecatedButton, + GlButton, GlLoadingIcon, }, directives: { @@ -82,16 +82,16 @@ export default { }; </script> <template> - <gl-deprecated-button + <gl-button :id="`js-ci-action-${link}`" v-gl-tooltip="{ boundary: 'viewport' }" :title="tooltipText" :class="cssClass" :disabled="isDisabled" - class="js-ci-action btn btn-blank btn-transparent ci-action-icon-container ci-action-icon-wrapper d-flex align-items-center justify-content-center" + class="js-ci-action ci-action-icon-container ci-action-icon-wrapper gl-display-flex gl-align-items-center gl-justify-content-center" @click="onClickAction" > <gl-loading-icon v-if="isLoading" class="js-action-icon-loading" /> <icon v-else :name="actionIcon" /> - </gl-deprecated-button> + </gl-button> </template> diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index 6b890688a48..f5bf6a6ed34 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -170,7 +170,7 @@ export default { v-for="(stage, index) in graph" :key="stage.name" :class="{ - 'has-upstream prepend-left-64': hasUpstream(index), + 'has-upstream gl-ml-11': hasUpstream(index), 'has-only-one-job': hasOnlyOneJob(stage), 'gl-mr-26': shouldAddRightMargin(index), }" diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue index 733553e02c0..f0a8f9f7ab7 100644 --- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue @@ -1,5 +1,5 @@ <script> -import { GlLoadingIcon, GlTooltipDirective, GlDeprecatedButton } from '@gitlab/ui'; +import { GlTooltipDirective, GlButton } from '@gitlab/ui'; import CiStatus from '~/vue_shared/components/ci_icon.vue'; import { __, sprintf } from '~/locale'; @@ -9,8 +9,7 @@ export default { }, components: { CiStatus, - GlLoadingIcon, - GlDeprecatedButton, + GlButton, }, props: { pipeline: { @@ -95,26 +94,21 @@ export default { @mouseover="onDownstreamHovered" @mouseleave="onDownstreamHoverLeave" > - <gl-deprecated-button + <gl-button :id="buttonId" v-gl-tooltip :title="tooltipText" - class="js-linked-pipeline-content linked-pipeline-content" + class="linked-pipeline-content" data-qa-selector="linked_pipeline_button" :class="`js-pipeline-expand-${pipeline.id}`" + :loading="pipeline.isLoading" @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" - /> + <ci-status v-if="!pipeline.isLoading" :status="pipelineStatus" css-classes="gl-top-0" /> <span class="str-truncated"> {{ downstreamTitle }} • #{{ pipeline.id }} </span> <div class="gl-pt-2"> <span class="badge badge-primary" data-testid="downstream-pipeline-label">{{ label }}</span> </div> - </gl-deprecated-button> + </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 index c4dfd3382a2..d82885ff8de 100644 --- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue @@ -27,7 +27,7 @@ export default { computed: { columnClass() { const positionValues = { - right: 'prepend-left-64', + right: 'gl-ml-11', left: 'gl-mr-7', }; return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`; diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue index dff642161db..c7b72be36ad 100644 --- a/app/assets/javascripts/pipelines/components/header_component.vue +++ b/app/assets/javascripts/pipelines/components/header_component.vue @@ -1,7 +1,6 @@ <script> -import { GlLoadingIcon, GlModal, GlModalDirective } from '@gitlab/ui'; +import { GlLoadingIcon, GlModal, GlModalDirective, GlButton } from '@gitlab/ui'; import ciHeader from '~/vue_shared/components/header_ci_component.vue'; -import LoadingButton from '~/vue_shared/components/loading_button.vue'; import eventHub from '../event_hub'; import { __ } from '~/locale'; @@ -13,7 +12,7 @@ export default { ciHeader, GlLoadingIcon, GlModal, - LoadingButton, + GlButton, }, directives: { GlModal: GlModalDirective, @@ -77,35 +76,43 @@ export default { :user="pipeline.user" item-name="Pipeline" > - <loading-button + <gl-button v-if="pipeline.retry_path" :loading="isRetrying" :disabled="isRetrying" - class="js-retry-button btn btn-inverted-secondary" - container-class="d-inline" - :label="__('Retry')" + data-testid="retryButton" + category="secondary" + variant="info" @click="retryPipeline()" - /> + > + {{ __('Retry') }} + </gl-button> - <loading-button + <gl-button v-if="pipeline.cancel_path" :loading="isCanceling" :disabled="isCanceling" - class="js-btn-cancel-pipeline btn btn-danger" - container-class="d-inline" - :label="__('Cancel running')" + data-testid="cancelPipeline" + class="gl-ml-3" + category="primary" + variant="danger" @click="cancelPipeline()" - /> + > + {{ __('Cancel running') }} + </gl-button> - <loading-button + <gl-button v-if="pipeline.delete_path" v-gl-modal="$options.DELETE_MODAL_ID" :loading="isDeleting" :disabled="isDeleting" - class="js-btn-delete-pipeline btn btn-danger btn-inverted" - container-class="d-inline" - :label="__('Delete')" - /> + data-testid="deletePipeline" + class="gl-ml-3" + category="secondary" + variant="danger" + > + {{ __('Delete') }} + </gl-button> </ci-header> <gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-3 gl-mb-3" /> 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 74ada6a4d15..fe8e3bd2b78 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue @@ -1,10 +1,10 @@ <script> -import { GlDeprecatedButton } from '@gitlab/ui'; +import { GlButton } from '@gitlab/ui'; export default { name: 'PipelinesEmptyState', components: { - GlDeprecatedButton, + GlButton, }, props: { helpPagePath: { @@ -43,13 +43,14 @@ export default { </p> <div class="text-center"> - <gl-deprecated-button + <gl-button :href="helpPagePath" - variant="primary" + variant="info" + category="primary" class="js-get-started-pipelines" > {{ s__('Pipelines|Get started with Pipelines') }} - </gl-deprecated-button> + </gl-button> </div> </template> 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 2905b2ca26f..f0614298bd3 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue @@ -1,27 +1,15 @@ <script> -import { GlLink, GlTooltipDirective } from '@gitlab/ui'; -import { escape } from 'lodash'; +import { GlLink, GlPopover, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; import { SCHEDULE_ORIGIN } from '../../constants'; -import { __, sprintf } from '~/locale'; -import popover from '~/vue_shared/directives/popover'; - -const popoverTitle = sprintf( - escape( - __( - `This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}`, - ), - ), - { strongStart: '<b>', strongEnd: '</b>' }, - false, -); export default { components: { GlLink, + GlPopover, + GlSprintf, }, directives: { GlTooltip: GlTooltipDirective, - popover, }, props: { pipeline: { @@ -44,23 +32,6 @@ export default { isScheduled() { return this.pipeline.source === SCHEDULE_ORIGIN; }, - popoverOptions() { - return { - html: true, - trigger: 'focus', - placement: 'top', - title: `<div class="autodevops-title"> - ${popoverTitle} - </div>`, - content: `<a - class="autodevops-link" - href="${this.autoDevopsHelpPath}" - target="_blank" - rel="noopener noreferrer nofollow"> - ${escape(__('Learn more about Auto DevOps'))} - </a>`, - }; - }, }, }; </script> @@ -114,13 +85,42 @@ export default { </span> <gl-link v-if="pipeline.flags.auto_devops" - v-popover="popoverOptions" + :id="`pipeline-url-autodevops-${pipeline.id}`" tabindex="0" class="js-pipeline-url-autodevops badge badge-info autodevops-badge" data-testid="pipeline-url-autodevops" role="button" >{{ __('Auto DevOps') }}</gl-link > + <gl-popover + :target="`pipeline-url-autodevops-${pipeline.id}`" + triggers="focus" + placement="top" + > + <template #title> + <div class="autodevops-title"> + <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 + class="autodevops-link" + :href="autoDevopsHelpPath" + target="_blank" + rel="noopener noreferrer nofollow" + > + {{ __('Learn more about Auto DevOps') }} + </gl-link> + </gl-popover> <span v-if="pipeline.flags.stuck" class="js-pipeline-url-stuck badge badge-warning" diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue index 0c531650fd2..2dfc6485d85 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue @@ -1,7 +1,7 @@ <script> import { isEqual } from 'lodash'; import { __, s__ } from '~/locale'; -import createFlash from '~/flash'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; import PipelinesService from '../../services/pipelines_service'; import pipelinesMixin from '../../mixins/pipelines'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue index 3009ca7a775..098efe68b83 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue @@ -1,7 +1,7 @@ <script> import { GlDeprecatedButton, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui'; import axios from '~/lib/utils/axios_utils'; -import flash from '~/flash'; +import { deprecatedCreateFlash as flash } from '~/flash'; import { s__, __, sprintf } from '~/locale'; import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; import Icon from '~/vue_shared/components/icon.vue'; diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue index 0505a8668d1..29345f33367 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue @@ -1,11 +1,11 @@ <script> import { GlFilteredSearch } from '@gitlab/ui'; +import { map } from 'lodash'; import { __, s__ } from '~/locale'; import PipelineTriggerAuthorToken from './tokens/pipeline_trigger_author_token.vue'; import PipelineBranchNameToken from './tokens/pipeline_branch_name_token.vue'; import PipelineStatusToken from './tokens/pipeline_status_token.vue'; import PipelineTagNameToken from './tokens/pipeline_tag_name_token.vue'; -import { map } from 'lodash'; export default { userType: 'username', diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue index 99492bd8357..d992a4b7752 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue @@ -15,7 +15,7 @@ import $ from 'jquery'; import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; import { __ } from '~/locale'; -import Flash from '~/flash'; +import { deprecatedCreateFlash as Flash } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import eventHub from '../../event_hub'; import Icon from '~/vue_shared/components/icon.vue'; diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue index b6eff2931d3..60cb697f1af 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue @@ -1,9 +1,9 @@ <script> import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui'; +import { debounce } from 'lodash'; import Api from '~/api'; import { FETCH_BRANCH_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../../constants'; -import createFlash from '~/flash'; -import { debounce } from 'lodash'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; export default { components: { diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue index 64de6d2a053..d6ba5fcca85 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue @@ -1,9 +1,9 @@ <script> import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui'; +import { debounce } from 'lodash'; import Api from '~/api'; import { FETCH_TAG_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../../constants'; -import createFlash from '~/flash'; -import { debounce } from 'lodash'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; export default { components: { diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue index b5aeb3fe9e0..dfa6d8c13a5 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue @@ -3,12 +3,12 @@ import { GlFilteredSearchToken, GlAvatar, GlFilteredSearchSuggestion, - GlDropdownDivider, + GlDeprecatedDropdownDivider, GlLoadingIcon, } from '@gitlab/ui'; -import Api from '~/api'; -import createFlash from '~/flash'; import { debounce } from 'lodash'; +import Api from '~/api'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; import { ANY_TRIGGER_AUTHOR, FETCH_AUTHOR_ERROR_MESSAGE, @@ -21,7 +21,7 @@ export default { GlFilteredSearchToken, GlAvatar, GlFilteredSearchSuggestion, - GlDropdownDivider, + GlDeprecatedDropdownDivider, GlLoadingIcon, }, props: { @@ -94,7 +94,7 @@ export default { <gl-filtered-search-suggestion :value="$options.anyTriggerAuthor">{{ $options.anyTriggerAuthor }}</gl-filtered-search-suggestion> - <gl-dropdown-divider /> + <gl-deprecated-dropdown-divider /> <gl-loading-icon v-if="loading" /> <template v-else> diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue b/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue index 8746784aa57..bc1d22e2976 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue @@ -14,7 +14,7 @@ export default { TestSummaryTable, }, computed: { - ...mapState(['hasFullReport', 'isLoading', 'selectedSuiteIndex', 'testReports']), + ...mapState(['isLoading', 'selectedSuiteIndex', 'testReports']), ...mapGetters(['getSelectedSuite']), showSuite() { return this.selectedSuiteIndex !== null; @@ -29,7 +29,7 @@ export default { }, methods: { ...mapActions([ - 'fetchFullReport', + 'fetchTestSuite', 'fetchSummary', 'setSelectedSuiteIndex', 'removeSelectedSuiteIndex', @@ -40,10 +40,8 @@ export default { summaryTableRowClick(index) { this.setSelectedSuiteIndex(index); - // Fetch the full report when the user clicks to see more details - if (!this.hasFullReport) { - this.fetchFullReport(); - } + // Fetch the test suite when the user clicks to see more details + this.fetchTestSuite(index); }, beforeEnterTransition() { document.documentElement.style.overflowX = 'hidden'; diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue index d57b1466177..478073e44d1 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue @@ -1,8 +1,8 @@ <script> import { mapGetters } from 'vuex'; +import { GlTooltipDirective, GlFriendlyWrap } from '@gitlab/ui'; import Icon from '~/vue_shared/components/icon.vue'; import { __ } from '~/locale'; -import { GlTooltipDirective } from '@gitlab/ui'; import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; export default { @@ -10,6 +10,7 @@ export default { components: { Icon, SmartVirtualList, + GlFriendlyWrap, }, directives: { GlTooltip: GlTooltipDirective, @@ -29,6 +30,7 @@ export default { }, maxShownRows: 30, typicalRowHeight: 75, + wrapSymbols: ['::', '#', '.', '_', '-', '/', '\\'], }; </script> @@ -71,23 +73,19 @@ export default { > <div class="table-section section-20 section-wrap"> <div role="rowheader" class="table-mobile-header">{{ __('Suite') }}</div> - <div - v-gl-tooltip - :title="testCase.classname" - class="table-mobile-content pr-md-1 text-truncate" - > - {{ testCase.classname }} + <div class="table-mobile-content pr-md-1 gl-overflow-wrap-break"> + <gl-friendly-wrap :symbols="$options.wrapSymbols" :text="testCase.classname" /> </div> </div> <div class="table-section section-20 section-wrap"> <div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div> - <div - v-gl-tooltip - :title="testCase.name" - class="table-mobile-content pr-md-1 text-truncate" - > - {{ testCase.name }} + <div class="table-mobile-content pr-md-1 gl-overflow-wrap-break"> + <gl-friendly-wrap + data-testid="caseName" + :symbols="$options.wrapSymbols" + :text="testCase.name" + /> </div> </div> diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue index 6cfb795595d..e774fe06fbe 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue @@ -1,7 +1,7 @@ <script> import { mapGetters } from 'vuex'; -import { s__ } from '~/locale'; import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; +import { s__ } from '~/locale'; import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; export default { diff --git a/app/assets/javascripts/pipelines/event_hub.js b/app/assets/javascripts/pipelines/event_hub.js index 0948c2e5352..e31806ad199 100644 --- a/app/assets/javascripts/pipelines/event_hub.js +++ b/app/assets/javascripts/pipelines/event_hub.js @@ -1,3 +1,3 @@ -import Vue from 'vue'; +import createEventHub from '~/helpers/event_hub_factory'; -export default new Vue(); +export default createEventHub(); diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_dag_vis_data.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_dag_vis_data.query.graphql new file mode 100644 index 00000000000..c73b186739e --- /dev/null +++ b/app/assets/javascripts/pipelines/graphql/queries/get_dag_vis_data.query.graphql @@ -0,0 +1,27 @@ +query getDagVisData($projectPath: ID!, $iid: ID!) { + project(fullPath: $projectPath) { + pipeline(iid: $iid) { + stages { + nodes { + name + groups { + nodes { + name + size + jobs { + nodes { + name + needs { + nodes { + name + } + } + } + } + } + } + } + } + } + } +} diff --git a/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js index f987c8f1dd4..886a8a78448 100644 --- a/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js +++ b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js @@ -1,4 +1,4 @@ -import flash from '~/flash'; +import { deprecatedCreateFlash as flash } from '~/flash'; import { __ } from '~/locale'; export default { diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js index 7710a96e5fb..e31545bba5c 100644 --- a/app/assets/javascripts/pipelines/mixins/pipelines.js +++ b/app/assets/javascripts/pipelines/mixins/pipelines.js @@ -1,7 +1,7 @@ import Visibility from 'visibilityjs'; import { GlLoadingIcon } from '@gitlab/ui'; import { __ } from '~/locale'; -import createFlash from '~/flash'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; import Poll from '~/lib/utils/poll'; import EmptyState from '../components/pipelines_list/empty_state.vue'; import SvgBlankState from '../components/pipelines_list/blank_state.vue'; diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index f1102a9bddf..c57be7c75b0 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -1,10 +1,10 @@ import Vue from 'vue'; -import Flash from '~/flash'; +import { deprecatedCreateFlash as Flash } from '~/flash'; import Translate from '~/vue_shared/translate'; import { __ } from '~/locale'; import { setUrlFragment, redirectTo } from '~/lib/utils/url_utility'; import pipelineGraph from './components/graph/graph_component.vue'; -import Dag from './components/dag/dag.vue'; +import createDagApp from './pipeline_details_dag'; import GraphBundleMixin from './mixins/graph_pipeline_bundle_mixin'; import PipelinesMediator from './pipeline_details_mediator'; import pipelineHeader from './components/header_component.vue'; @@ -92,48 +92,15 @@ const createPipelineHeaderApp = mediator => { }); }; -const createPipelinesTabs = testReportsStore => { - const tabsElement = document.querySelector('.pipelines-tabs'); - - if (tabsElement) { - const fetchReportsAction = 'fetchFullReport'; - const isTestTabActive = Boolean( - document.querySelector('.pipelines-tabs > li > a.test-tab.active'), - ); - - if (isTestTabActive) { - testReportsStore.dispatch(fetchReportsAction); - } else { - const tabClickHandler = e => { - if (e.target.className === 'test-tab') { - testReportsStore.dispatch(fetchReportsAction); - tabsElement.removeEventListener('click', tabClickHandler); - } - }; - - tabsElement.addEventListener('click', tabClickHandler); - } - } -}; - const createTestDetails = () => { - if (!window.gon?.features?.junitPipelineView) { - return; - } - const el = document.querySelector('#js-pipeline-tests-detail'); - const { fullReportEndpoint, summaryEndpoint, countEndpoint } = el?.dataset || {}; + const { summaryEndpoint, suiteEndpoint } = el?.dataset || {}; const testReportsStore = createTestReportsStore({ - fullReportEndpoint, - summaryEndpoint: summaryEndpoint || countEndpoint, - useBuildSummaryReport: window.gon?.features?.buildReportSummary, + summaryEndpoint, + suiteEndpoint, }); - if (!window.gon?.features?.buildReportSummary) { - createPipelinesTabs(testReportsStore); - } - // eslint-disable-next-line no-new new Vue({ el, @@ -147,32 +114,6 @@ const createTestDetails = () => { }); }; -const createDagApp = () => { - if (!window.gon?.features?.dagPipelineTab) { - return; - } - - const el = document.querySelector('#js-pipeline-dag-vue'); - const { pipelineDataPath, emptySvgPath, dagDocPath } = el?.dataset; - - // eslint-disable-next-line no-new - new Vue({ - el, - components: { - Dag, - }, - render(createElement) { - return createElement('dag', { - props: { - graphUrl: pipelineDataPath, - emptySvgPath, - dagDocPath, - }, - }); - }, - }); -}; - export default () => { const { dataset } = document.querySelector('.js-pipeline-details-vue'); const mediator = new PipelinesMediator({ endpoint: dataset.endpoint }); diff --git a/app/assets/javascripts/pipelines/pipeline_details_dag.js b/app/assets/javascripts/pipelines/pipeline_details_dag.js new file mode 100644 index 00000000000..dc03b457265 --- /dev/null +++ b/app/assets/javascripts/pipelines/pipeline_details_dag.js @@ -0,0 +1,39 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import Dag from './components/dag/dag.vue'; + +Vue.use(VueApollo); + +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), +}); + +const createDagApp = () => { + if (!window.gon?.features?.dagPipelineTab) { + return; + } + + const el = document.querySelector('#js-pipeline-dag-vue'); + const { pipelineProjectPath, pipelineIid, emptySvgPath, dagDocPath } = el?.dataset; + + // eslint-disable-next-line no-new + new Vue({ + el, + components: { + Dag, + }, + apolloProvider, + provide: { + pipelineProjectPath, + pipelineIid, + emptySvgPath, + dagDocPath, + }, + render(createElement) { + return createElement('dag', {}); + }, + }); +}; + +export default createDagApp; diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediator.js b/app/assets/javascripts/pipelines/pipeline_details_mediator.js index f3387f00fc1..d487970aed7 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_mediator.js +++ b/app/assets/javascripts/pipelines/pipeline_details_mediator.js @@ -1,6 +1,6 @@ import Visibility from 'visibilityjs'; import PipelineStore from './stores/pipeline_store'; -import Flash from '../flash'; +import { deprecatedCreateFlash as Flash } from '../flash'; import Poll from '../lib/utils/poll'; import { __ } from '../locale'; import PipelineService from './services/pipeline_service'; diff --git a/app/assets/javascripts/pipelines/stores/test_reports/actions.js b/app/assets/javascripts/pipelines/stores/test_reports/actions.js index ccacb9f7e97..f10bbeec77c 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/actions.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/actions.js @@ -1,46 +1,46 @@ import axios from '~/lib/utils/axios_utils'; import * as types from './mutation_types'; -import createFlash from '~/flash'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; import { s__ } from '~/locale'; export const fetchSummary = ({ state, commit, dispatch }) => { - // If we do this without the build_report_summary feature flag enabled - // it causes a race condition for toggleLoading and ruins the loading - // state in the application - if (state.useBuildSummaryReport) { - dispatch('toggleLoading'); - } + dispatch('toggleLoading'); return axios .get(state.summaryEndpoint) .then(({ data }) => { commit(types.SET_SUMMARY, data); - - if (!state.useBuildSummaryReport) { - // Set the tab counter badge to total_count - // This is temporary until we can server-side render that count number - // (see https://gitlab.com/gitlab-org/gitlab/-/issues/223134) - document.querySelector('.js-test-report-badge-counter').innerHTML = data.total_count || 0; - } }) .catch(() => { createFlash(s__('TestReports|There was an error fetching the summary.')); }) .finally(() => { - if (state.useBuildSummaryReport) { - dispatch('toggleLoading'); - } + dispatch('toggleLoading'); }); }; -export const fetchFullReport = ({ state, commit, dispatch }) => { +export const fetchTestSuite = ({ state, commit, dispatch }, index) => { + const { hasFullSuite } = state.testReports?.test_suites?.[index] || {}; + // We don't need to fetch the suite if we have the information already + if (hasFullSuite) { + return Promise.resolve(); + } + dispatch('toggleLoading'); + const { name = '', build_ids = [] } = state.testReports?.test_suites?.[index] || {}; + // Replacing `/:suite_name.json` with the name of the suite. Including the extra characters + // to ensure that we replace exactly the template part of the URL string + const endpoint = state.suiteEndpoint?.replace( + '/:suite_name.json', + `/${encodeURIComponent(name)}.json`, + ); + return axios - .get(state.fullReportEndpoint) - .then(({ data }) => commit(types.SET_REPORTS, data)) + .get(endpoint, { params: { build_ids } }) + .then(({ data }) => commit(types.SET_SUITE, { suite: data, index })) .catch(() => { - createFlash(s__('TestReports|There was an error fetching the test reports.')); + createFlash(s__('TestReports|There was an error fetching the test suite.')); }) .finally(() => { dispatch('toggleLoading'); @@ -52,6 +52,3 @@ export const setSelectedSuiteIndex = ({ commit }, data) => export const removeSelectedSuiteIndex = ({ commit }) => commit(types.SET_SELECTED_SUITE_INDEX, null); export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/pipelines/stores/test_reports/getters.js b/app/assets/javascripts/pipelines/stores/test_reports/getters.js index 877762b77c9..6c670806cc4 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/getters.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/getters.js @@ -16,6 +16,3 @@ export const getSuiteTests = state => { const { test_cases: testCases = [] } = getSelectedSuite(state); return testCases.sort(sortTestCases).map(addIconStatus); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js index 76405557b51..52345888cb0 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js @@ -1,4 +1,4 @@ -export const SET_REPORTS = 'SET_REPORTS'; export const SET_SELECTED_SUITE_INDEX = 'SET_SELECTED_SUITE_INDEX'; export const SET_SUMMARY = 'SET_SUMMARY'; +export const SET_SUITE = 'SET_SUITE'; export const TOGGLE_LOADING = 'TOGGLE_LOADING'; diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js index 2531ab1e87c..3652a12a6ba 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js @@ -1,16 +1,41 @@ import * as types from './mutation_types'; export default { - [types.SET_REPORTS](state, testReports) { - Object.assign(state, { testReports, hasFullReport: true }); + [types.SET_SUITE](state, { suite = {}, index = null }) { + state.testReports.test_suites[index] = { ...suite, hasFullSuite: true }; }, [types.SET_SELECTED_SUITE_INDEX](state, selectedSuiteIndex) { Object.assign(state, { selectedSuiteIndex }); }, - [types.SET_SUMMARY](state, summary) { - Object.assign(state, { testReports: { ...state.testReports, ...summary } }); + [types.SET_SUMMARY](state, testReports) { + const { total } = testReports; + state.testReports = { + ...testReports, + + /* + TLDR; this is a temporary mapping that will be updated once + test suites have the new data schema + + The backend is in the middle of updating the data schema + to have a `total` object containing the total data values. + The test suites don't have the new schema, but the summary + does. Currently the `test_summary.vue` component takes both + the summary and a test suite depending on what is being viewed. + This is a temporary change to map the new schema to the old until + we can update the schema for the test suites also. + Since test suites is an array, it is easier to just map the summary + to the old schema instead of mapping every test suite to the new. + */ + + total_time: total.time, + total_count: total.count, + success_count: total.success, + failed_count: total.failed, + skipped_count: total.skipped, + error_count: total.error, + }; }, [types.TOGGLE_LOADING](state) { diff --git a/app/assets/javascripts/pipelines/stores/test_reports/state.js b/app/assets/javascripts/pipelines/stores/test_reports/state.js index bcf5c147916..af79521d68a 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/state.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/state.js @@ -1,13 +1,7 @@ -export default ({ - fullReportEndpoint = '', - summaryEndpoint = '', - useBuildSummaryReport = false, -}) => ({ +export default ({ summaryEndpoint = '', suiteEndpoint = '' }) => ({ summaryEndpoint, - fullReportEndpoint, + suiteEndpoint, testReports: {}, selectedSuiteIndex: null, - hasFullReport: false, isLoading: false, - useBuildSummaryReport, }); diff --git a/app/assets/javascripts/pipelines/utils.js b/app/assets/javascripts/pipelines/utils.js index 9dbc8073d3a..2e08001f6b3 100644 --- a/app/assets/javascripts/pipelines/utils.js +++ b/app/assets/javascripts/pipelines/utils.js @@ -1,8 +1,7 @@ import { pickBy } from 'lodash'; import { SUPPORTED_FILTER_PARAMETERS } from './constants'; +// eslint-disable-next-line import/prefer-default-export export const validateParams = params => { return pickBy(params, (val, key) => SUPPORTED_FILTER_PARAMETERS.includes(key) && val); }; - -export default () => {}; |