diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-19 08:27:35 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-19 08:27:35 +0000 |
commit | 7e9c479f7de77702622631cff2628a9c8dcbc627 (patch) | |
tree | c8f718a08e110ad7e1894510980d2155a6549197 /app/assets/javascripts/pipelines/components | |
parent | e852b0ae16db4052c1c567d9efa4facc81146e88 (diff) | |
download | gitlab-ce-7e9c479f7de77702622631cff2628a9c8dcbc627.tar.gz |
Add latest changes from gitlab-org/gitlab@13-6-stable-eev13.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/pipelines/components')
18 files changed, 268 insertions, 114 deletions
diff --git a/app/assets/javascripts/pipelines/components/dag/dag.vue b/app/assets/javascripts/pipelines/components/dag/dag.vue index 6267b63328c..85171263f08 100644 --- a/app/assets/javascripts/pipelines/components/dag/dag.vue +++ b/app/assets/javascripts/pipelines/components/dag/dag.vue @@ -1,5 +1,5 @@ <script> -import { GlAlert, GlButton, GlEmptyState, GlSprintf } from '@gitlab/ui'; +import { GlAlert, GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; import { isEmpty } from 'lodash'; import { __ } from '~/locale'; import { fetchPolicies } from '~/lib/graphql'; @@ -17,11 +17,15 @@ export default { DagAnnotations, DagGraph, GlAlert, - GlSprintf, - GlEmptyState, GlButton, + GlEmptyState, + GlLink, + GlSprintf, }, inject: { + aboutDagDocPath: { + default: null, + }, dagDocPath: { default: null, }, @@ -89,14 +93,14 @@ export default { [DEFAULT]: __('An unknown error occurred while loading this graph.'), }, emptyStateTexts: { - title: __('Start using Directed Acyclic Graphs (DAG)'), + title: __('Speed up your pipelines with Needs relationships'), firstDescription: __( - "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph.", + 'Using the %{codeStart}needs%{codeEnd} keyword makes jobs run before their stage is reached. Jobs run as soon as their %{codeStart}needs%{codeEnd} relationships are met, which speeds up your pipelines.', ), secondDescription: __( - 'Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines.', + "If you add %{codeStart}needs%{codeEnd} to jobs in your pipeline you'll be able to view the %{codeStart}needs%{codeEnd} relationships between jobs in this tab as a %{linkStart}Directed Acyclic Graph (DAG)%{linkEnd}.", ), - button: __('Learn more about job dependencies'), + button: __('Learn more about Needs relationships'), }, computed: { failure() { @@ -222,6 +226,9 @@ export default { <template #code="{ content }"> <code>{{ content }}</code> </template> + <template #link="{ content }"> + <gl-link :href="aboutDagDocPath">{{ content }}</gl-link> + </template> </gl-sprintf> </p> </div> diff --git a/app/assets/javascripts/pipelines/components/graph/constants.js b/app/assets/javascripts/pipelines/components/graph/constants.js new file mode 100644 index 00000000000..ba1922b6dae --- /dev/null +++ b/app/assets/javascripts/pipelines/components/graph/constants.js @@ -0,0 +1,3 @@ +export const DOWNSTREAM = 'downstream'; +export const MAIN = 'main'; +export const UPSTREAM = 'upstream'; diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index 0f5a8cb8fbf..16ce279a591 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -5,6 +5,7 @@ import StageColumnComponent from './stage_column_component.vue'; import GraphWidthMixin from '../../mixins/graph_width_mixin'; import LinkedPipelinesColumn from './linked_pipelines_column.vue'; import GraphBundleMixin from '../../mixins/graph_pipeline_bundle_mixin'; +import { UPSTREAM, DOWNSTREAM, MAIN } from './constants'; export default { name: 'PipelineGraph', @@ -35,11 +36,11 @@ export default { type: { type: String, required: false, - default: 'main', + default: MAIN, }, }, - upstream: 'upstream', - downstream: 'downstream', + upstream: UPSTREAM, + downstream: DOWNSTREAM, data() { return { downstreamMarginTop: null, @@ -54,41 +55,41 @@ export default { graph() { return this.pipeline.details?.stages; }, - hasTriggeredBy() { + hasUpstream() { return ( this.type !== this.$options.downstream && - this.triggeredByPipelines && + this.upstreamPipelines && this.pipeline.triggered_by !== null ); }, - triggeredByPipelines() { + upstreamPipelines() { return this.pipeline.triggered_by; }, - hasTriggered() { + hasDownstream() { return ( this.type !== this.$options.upstream && - this.triggeredPipelines && + this.downstreamPipelines && this.pipeline.triggered.length > 0 ); }, - triggeredPipelines() { + downstreamPipelines() { return this.pipeline.triggered; }, - expandedTriggeredBy() { + expandedUpstream() { return ( this.pipeline.triggered_by && Array.isArray(this.pipeline.triggered_by) && this.pipeline.triggered_by.find(el => el.isExpanded) ); }, - expandedTriggered() { + expandedDownstream() { return this.pipeline.triggered && this.pipeline.triggered.find(el => el.isExpanded); }, pipelineTypeUpstream() { - return this.type !== this.$options.downstream && this.expandedTriggeredBy; + return this.type !== this.$options.downstream && this.expandedUpstream; }, pipelineTypeDownstream() { - return this.type !== this.$options.upstream && this.expandedTriggered; + return this.type !== this.$options.upstream && this.expandedDownstream; }, pipelineProjectId() { return this.pipeline.project.id; @@ -142,11 +143,11 @@ export default { * and we want to reset the pipeline store. Triggering the reset without * this condition would mean not allowing downstreams of downstreams to expand */ - if (this.expandedTriggered?.id !== pipeline.id) { - this.$emit('onResetTriggered', this.pipeline, pipeline); + if (this.expandedDownstream?.id !== pipeline.id) { + this.$emit('onResetDownstream', this.pipeline, pipeline); } - this.$emit('onClickTriggered', pipeline); + this.$emit('onClickDownstreamPipeline', pipeline); }, calculateMarginTop(downstreamNode, pixelDiff) { return `${downstreamNode.offsetTop - downstreamNode.offsetParent.offsetTop - pixelDiff}px`; @@ -154,8 +155,8 @@ export default { hasOnlyOneJob(stage) { return stage.groups.length === 1; }, - hasUpstream(index) { - return index === 0 && this.hasTriggeredBy; + hasUpstreamColumn(index) { + return index === 0 && this.hasUpstream; }, setJob(jobName) { this.jobName = jobName; @@ -192,30 +193,30 @@ export default { <pipeline-graph v-if="pipelineTypeUpstream" - type="upstream" + :type="$options.upstream" class="d-inline-block upstream-pipeline" - :class="`js-upstream-pipeline-${expandedTriggeredBy.id}`" + :class="`js-upstream-pipeline-${expandedUpstream.id}`" :is-loading="false" - :pipeline="expandedTriggeredBy" + :pipeline="expandedUpstream" :is-linked-pipeline="true" :mediator="mediator" - @onClickTriggeredBy="clickTriggeredByPipeline" + @onClickUpstreamPipeline="clickUpstreamPipeline" @refreshPipelineGraph="requestRefreshPipelineGraph" /> <linked-pipelines-column - v-if="hasTriggeredBy" - :linked-pipelines="triggeredByPipelines" + v-if="hasUpstream" + :type="$options.upstream" + :linked-pipelines="upstreamPipelines" :column-title="__('Upstream')" :project-id="pipelineProjectId" - graph-position="left" - @linkedPipelineClick="$emit('onClickTriggeredBy', $event)" + @linkedPipelineClick="$emit('onClickUpstreamPipeline', $event)" /> <ul v-if="!isLoading" :class="{ - 'inline js-has-linked-pipelines': hasTriggered || hasTriggeredBy, + 'inline js-has-linked-pipelines': hasDownstream || hasUpstream, }" class="stage-column-list align-top" > @@ -223,7 +224,7 @@ export default { v-for="(stage, index) in graph" :key="stage.name" :class="{ - 'has-upstream gl-ml-11': hasUpstream(index), + 'has-upstream gl-ml-11': hasUpstreamColumn(index), 'has-only-one-job': hasOnlyOneJob(stage), 'gl-mr-26': shouldAddRightMargin(index), }" @@ -231,7 +232,7 @@ export default { :groups="stage.groups" :stage-connector-class="stageConnectorClass(index, stage)" :is-first-column="isFirstColumn(index)" - :has-triggered-by="hasTriggeredBy" + :has-upstream="hasUpstream" :action="stage.status.action" :job-hovered="jobName" :pipeline-expanded="pipelineExpanded" @@ -240,11 +241,11 @@ export default { </ul> <linked-pipelines-column - v-if="hasTriggered" - :linked-pipelines="triggeredPipelines" + v-if="hasDownstream" + :type="$options.downstream" + :linked-pipelines="downstreamPipelines" :column-title="__('Downstream')" :project-id="pipelineProjectId" - graph-position="right" @linkedPipelineClick="handleClickedDownstream" @downstreamHovered="setJob" @pipelineExpandToggle="setPipelineExpanded" @@ -252,15 +253,15 @@ export default { <pipeline-graph v-if="pipelineTypeDownstream" - type="downstream" + :type="$options.downstream" class="d-inline-block" - :class="`js-downstream-pipeline-${expandedTriggered.id}`" + :class="`js-downstream-pipeline-${expandedDownstream.id}`" :is-loading="false" - :pipeline="expandedTriggered" + :pipeline="expandedDownstream" :is-linked-pipeline="true" :style="{ 'margin-top': downstreamMarginTop }" :mediator="mediator" - @onClickTriggered="clickTriggeredPipeline" + @onClickDownstreamPipeline="clickDownstreamPipeline" @refreshPipelineGraph="requestRefreshPipelineGraph" /> </div> diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue index 7aee2573ce1..4ed0aae0d1e 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_item.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue @@ -119,6 +119,9 @@ export default { }, }, methods: { + hideTooltips() { + this.$root.$emit('bv::hide::tooltip'); + }, pipelineActionRequestComplete() { this.$emit('pipelineActionRequestComplete'); }, @@ -129,24 +132,26 @@ export default { <div class="ci-job-component" data-qa-selector="job_item_container"> <gl-link v-if="status.has_details" - v-gl-tooltip="{ boundary, placement: 'bottom' }" + v-gl-tooltip="{ boundary, placement: 'bottom', customClass: 'gl-pointer-events-none' }" :href="status.details_path" :title="tooltipText" :class="jobClasses" class="js-pipeline-graph-job-link qa-job-link menu-item" data-testid="job-with-link" - @click.stop + @click.stop="hideTooltips" + @mouseout="hideTooltips" > <job-name-component :name="job.name" :status="job.status" /> </gl-link> <div v-else - v-gl-tooltip="{ boundary, placement: 'bottom' }" + v-gl-tooltip="{ boundary, placement: 'bottom', customClass: 'gl-pointer-events-none' }" :title="tooltipText" :class="jobClasses" class="js-job-component-tooltip non-details-job-component" data-testid="job-without-link" + @mouseout="hideTooltips" > <job-name-component :name="job.name" :status="job.status" /> </div> diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue index e359fc787c5..11f06a25984 100644 --- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue @@ -2,6 +2,7 @@ import { GlTooltipDirective, GlButton, GlLink, GlLoadingIcon } from '@gitlab/ui'; import CiStatus from '~/vue_shared/components/ci_icon.vue'; import { __, sprintf } from '~/locale'; +import { UPSTREAM, DOWNSTREAM } from './constants'; export default { directives: { @@ -14,6 +15,10 @@ export default { GlLoadingIcon, }, props: { + columnTitle: { + type: String, + required: true, + }, pipeline: { type: Object, required: true, @@ -22,7 +27,7 @@ export default { type: Number, required: true, }, - columnTitle: { + type: { type: String, required: true, }, @@ -50,12 +55,10 @@ export default { return this.childPipeline ? __('child-pipeline') : this.pipeline.project.name; }, parentPipeline() { - // Refactor string match when BE returns Upstream/Downstream indicators - return this.projectId === this.pipeline.project.id && this.columnTitle === __('Upstream'); + return this.isUpstream && this.isSameProject; }, childPipeline() { - // Refactor string match when BE returns Upstream/Downstream indicators - return this.projectId === this.pipeline.project.id && this.isDownstream; + return this.isDownstream && this.isSameProject; }, label() { if (this.parentPipeline) { @@ -66,7 +69,13 @@ export default { return __('Multi-project'); }, isDownstream() { - return this.columnTitle === __('Downstream'); + return this.type === DOWNSTREAM; + }, + isUpstream() { + return this.type === UPSTREAM; + }, + isSameProject() { + return this.projectId === this.pipeline.project.id; }, sourceJobInfo() { return this.isDownstream @@ -74,13 +83,13 @@ export default { : ''; }, expandedIcon() { - if (this.parentPipeline) { + if (this.isUpstream) { return this.expanded ? 'angle-right' : 'angle-left'; } return this.expanded ? 'angle-left' : 'angle-right'; }, expandButtonPosition() { - return this.parentPipeline ? 'gl-left-0 gl-border-r-1!' : 'gl-right-0 gl-border-l-1!'; + return this.isUpstream ? 'gl-left-0 gl-border-r-1!' : 'gl-right-0 gl-border-l-1!'; }, }, methods: { @@ -116,7 +125,7 @@ export default { > <div class="gl-relative gl-bg-white gl-p-3 gl-border-solid gl-border-gray-100 gl-border-1" - :class="{ 'gl-pl-9': parentPipeline }" + :class="{ 'gl-pl-9': isUpstream }" > <div class="gl-display-flex"> <ci-status 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 3ad28d88345..2ca33e6d33e 100644 --- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue @@ -1,6 +1,6 @@ <script> import LinkedPipeline from './linked_pipeline.vue'; -import { __ } from '~/locale'; +import { UPSTREAM } from './constants'; export default { components: { @@ -15,7 +15,7 @@ export default { type: Array, required: true, }, - graphPosition: { + type: { type: String, required: true, }, @@ -32,9 +32,12 @@ export default { }; return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`; }, + graphPosition() { + return this.isUpstream ? 'left' : 'right'; + }, // Refactor string match when BE returns Upstream/Downstream indicators isUpstream() { - return this.columnTitle === __('Upstream'); + return this.type === UPSTREAM; }, }, methods: { @@ -45,6 +48,11 @@ export default { this.$emit('downstreamHovered', jobName); }, onPipelineExpandToggle(jobName, expanded) { + // Highlighting only applies to downstream pipelines + if (this.isUpstream) { + return; + } + this.$emit('pipelineExpandToggle', jobName, expanded); }, }, @@ -66,6 +74,7 @@ export default { :pipeline="pipeline" :column-title="columnTitle" :project-id="projectId" + :type="type" @pipelineClicked="onPipelineClick($event, pipeline, index)" @downstreamHovered="onDownstreamHovered" @pipelineExpandToggle="onPipelineExpandToggle" diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue index b26f28fa6af..741609c908a 100644 --- a/app/assets/javascripts/pipelines/components/header_component.vue +++ b/app/assets/javascripts/pipelines/components/header_component.vue @@ -1,16 +1,22 @@ <script> import { GlAlert, GlButton, GlLoadingIcon, GlModal, GlModalDirective } from '@gitlab/ui'; import { __ } from '~/locale'; -import axios from '~/lib/utils/axios_utils'; import ciHeader from '~/vue_shared/components/header_ci_component.vue'; import { setUrlFragment, redirectTo } from '~/lib/utils/url_utility'; import getPipelineQuery from '../graphql/queries/get_pipeline_header_data.query.graphql'; +import deletePipelineMutation from '../graphql/mutations/delete_pipeline.mutation.graphql'; +import retryPipelineMutation from '../graphql/mutations/retry_pipeline.mutation.graphql'; +import cancelPipelineMutation from '../graphql/mutations/cancel_pipeline.mutation.graphql'; import { LOAD_FAILURE, POST_FAILURE, DELETE_FAILURE, DEFAULT } from '../constants'; const DELETE_MODAL_ID = 'pipeline-delete-modal'; +const POLL_INTERVAL = 10000; export default { name: 'PipelineHeaderSection', + pipelineCancel: 'pipelineCancel', + pipelineRetry: 'pipelineRetry', + finishedStatuses: ['FAILED', 'SUCCESS', 'CANCELED'], components: { ciHeader, GlAlert, @@ -28,7 +34,7 @@ export default { [DEFAULT]: __('An unknown error occurred.'), }, inject: { - // Receive `cancel`, `delete`, `fullProject` and `retry` + // Receive `fullProject` and `pipelinesPath` paths: { default: {}, }, @@ -52,7 +58,7 @@ export default { error() { this.reportFailure(LOAD_FAILURE); }, - pollInterval: 10000, + pollInterval: POLL_INTERVAL, watchLoading(isLoading) { if (!isLoading) { // To ensure apollo has updated the cache, @@ -90,6 +96,9 @@ export default { status() { return this.pipeline?.status; }, + isFinished() { + return this.$options.finishedStatuses.includes(this.status); + }, shouldRenderContent() { return !this.isLoadingInitialQuery && this.hasPipelineData; }, @@ -118,35 +127,72 @@ export default { } }, }, + watch: { + isFinished(finished) { + if (finished) { + this.$apollo.queries.pipeline.stopPolling(); + } + }, + }, methods: { reportFailure(errorType) { this.failureType = errorType; }, - async postAction(path) { + async postPipelineAction(name, mutation) { try { - await axios.post(path); - this.$apollo.queries.pipeline.refetch(); + const { + data: { + [name]: { errors }, + }, + } = await this.$apollo.mutate({ + mutation, + variables: { id: this.pipeline.id }, + }); + + if (errors.length > 0) { + this.reportFailure(POST_FAILURE); + } else { + await this.$apollo.queries.pipeline.refetch(); + if (!this.isFinished) { + this.$apollo.queries.pipeline.startPolling(POLL_INTERVAL); + } + } } catch { this.reportFailure(POST_FAILURE); } }, - async cancelPipeline() { + cancelPipeline() { this.isCanceling = true; - this.postAction(this.paths.cancel); + this.postPipelineAction(this.$options.pipelineCancel, cancelPipelineMutation); }, - async retryPipeline() { + retryPipeline() { this.isRetrying = true; - this.postAction(this.paths.retry); + this.postPipelineAction(this.$options.pipelineRetry, retryPipelineMutation); }, async deletePipeline() { this.isDeleting = true; this.$apollo.queries.pipeline.stopPolling(); try { - const { request } = await axios.delete(this.paths.delete); - redirectTo(setUrlFragment(request.responseURL, 'delete_success')); + const { + data: { + pipelineDestroy: { errors }, + }, + } = await this.$apollo.mutate({ + mutation: deletePipelineMutation, + variables: { + id: this.pipeline.id, + }, + }); + + if (errors.length > 0) { + this.reportFailure(DELETE_FAILURE); + this.isDeleting = false; + } else { + redirectTo(setUrlFragment(this.paths.pipelinesPath, 'delete_success')); + } } catch { - this.$apollo.queries.pipeline.startPolling(); + this.$apollo.queries.pipeline.startPolling(POLL_INTERVAL); this.reportFailure(DELETE_FAILURE); this.isDeleting = false; } diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue index 8eec0110865..a0c35f54c0e 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue @@ -57,7 +57,7 @@ export default { <tooltip-on-truncate :title="jobName" truncate-target="child" placement="top"> <div :id="jobId" - class="pipeline-job-pill gl-bg-white gl-text-center gl-text-truncate gl-rounded-pill gl-mb-3 gl-px-5 gl-py-2 gl-relative gl-z-index-1 gl-transition-duration-slow gl-transition-timing-function-ease" + class="gl-w-15 gl-bg-white gl-text-center gl-text-truncate gl-rounded-pill gl-mb-3 gl-px-5 gl-py-2 gl-relative gl-z-index-1 gl-transition-duration-slow gl-transition-timing-function-ease" :class="jobPillClasses" @mouseover="onMouseEnter" @mouseleave="onMouseLeave" 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 3a2b8a20bae..11ad2f2a3b6 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue @@ -97,15 +97,20 @@ export default { this.reportFailure(DRAW_FAILURE); } }, - getStageBackgroundClass(index) { + getStageBackgroundClasses(index) { const { length } = this.pipelineData.stages; - + // 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) { - return 'stage-rounded'; - } else if (index === 0) { - return 'stage-left-rounded'; - } else if (index === length - 1) { - return 'stage-right-rounded'; + return 'gl-rounded-bottom-left-6 gl-rounded-top-left-6 gl-rounded-bottom-right-6 gl-rounded-top-right-6'; + } + + if (index === 0) { + return 'gl-rounded-bottom-left-6 gl-rounded-top-left-6'; + } + + if (index === length - 1) { + return 'gl-rounded-bottom-right-6 gl-rounded-top-right-6'; } return ''; @@ -162,7 +167,11 @@ export default { {{ failure.text }} </gl-alert> <gl-alert v-if="isPipelineDataEmpty" variant="tip" :dismissible="false"> - {{ __('No content to show') }} + {{ + __( + 'The visualization will appear in this tab when the CI/CD configuration file is populated with valid syntax.', + ) + }} </gl-alert> <div v-else @@ -190,7 +199,8 @@ export default { > <div class="gl-display-flex gl-align-items-center gl-bg-white gl-w-full gl-px-8 gl-py-4 gl-mb-5" - :class="getStageBackgroundClass(index)" + :class="getStageBackgroundClasses(index)" + data-testid="stage-background" > <stage-pill :stage-name="stage.name" :is-empty="stage.groups.length === 0" /> </div> diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/stage_pill.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/stage_pill.vue index 7b2458db725..df48426f24e 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/stage_pill.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/stage_pill.vue @@ -26,7 +26,7 @@ export default { <template> <tooltip-on-truncate :title="stageName" truncate-target="child" placement="top"> <div - class="gl-px-5 gl-py-2 gl-text-white gl-text-center gl-text-truncate gl-rounded-pill pipeline-stage-pill" + class="gl-px-5 gl-py-2 gl-text-white gl-text-center gl-text-truncate gl-rounded-pill gl-w-20" :class="emptyClass" > {{ stageName }} diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue index adba86d384b..9ee427d01fd 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue @@ -13,7 +13,6 @@ import CIPaginationMixin from '~/vue_shared/mixins/ci_pagination_api_mixin'; import PipelinesFilteredSearch from './pipelines_filtered_search.vue'; import { validateParams } from '../../utils'; import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING, FILTER_TAG_IDENTIFIER } from '../../constants'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { components: { @@ -23,7 +22,7 @@ export default { PipelinesFilteredSearch, GlIcon, }, - mixins: [pipelinesMixin, CIPaginationMixin, glFeatureFlagsMixin()], + mixins: [pipelinesMixin, CIPaginationMixin], props: { store: { type: Object, @@ -209,9 +208,6 @@ export default { }, ]; }, - canFilterPipelines() { - return this.glFeatures.filterPipelinesSearch; - }, validatedParams() { return validateParams(this.params); }, @@ -306,7 +302,6 @@ export default { </div> <pipelines-filtered-search - v-if="canFilterPipelines" :project-id="projectId" :params="validatedParams" @filterPipelines="filterPipelines" @@ -342,7 +337,7 @@ export default { :message="emptyTabMessage" /> - <div v-else-if="stateToRender === $options.stateMap.tableList" class="table-holder"> + <div v-else-if="stateToRender === $options.stateMap.tableList"> <pipelines-table-component :pipelines="state.pipelines" :pipeline-schedule-url="pipelineScheduleUrl" 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 97595e5d2ce..e52afe08336 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue @@ -87,7 +87,7 @@ export default { :aria-label="__('Run manual or delayed jobs')" > <gl-icon name="play" class="icon-play" /> - <i class="fa fa-caret-down" aria-hidden="true"></i> + <gl-icon name="chevron-down" /> <gl-loading-icon v-if="isLoading" /> </button> 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 4a3d134685e..55c71e299be 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue @@ -29,7 +29,7 @@ export default { :aria-label="__('Artifacts')" > <gl-icon name="download" /> - <i class="fa fa-caret-down" aria-hidden="true"></i> + <gl-icon name="chevron-down" /> </button> <ul class="dropdown-menu dropdown-menu-right"> <li v-for="(artifact, i) in artifacts" :key="i"> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue index 4045f450104..581ea5fbb35 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue @@ -168,7 +168,7 @@ export default { aria-expanded="false" @click="onClickStage" > - <span :aria-label="stage.title" aria-hidden="true" class="no-pointer-events"> + <span :aria-label="stage.title" aria-hidden="true" class="gl-pointer-events-none"> <gl-icon :name="borderlessIcon" /> </span> </button> 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 dd09247337c..1d117cfe34a 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue @@ -1,6 +1,5 @@ <script> import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; -import '~/lib/utils/datetime_utility'; import timeagoMixin from '~/vue_shared/mixins/timeago'; export default { diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_case_details.vue b/app/assets/javascripts/pipelines/components/test_reports/test_case_details.vue new file mode 100644 index 00000000000..504cf138d07 --- /dev/null +++ b/app/assets/javascripts/pipelines/components/test_reports/test_case_details.vue @@ -0,0 +1,67 @@ +<script> +import { GlModal } from '@gitlab/ui'; +import { __ } from '~/locale'; +import CodeBlock from '~/vue_shared/components/code_block.vue'; + +export default { + name: 'TestCaseDetails', + components: { + CodeBlock, + GlModal, + }, + props: { + modalId: { + type: String, + required: true, + }, + testCase: { + type: Object, + required: true, + validator: ({ classname, formattedTime, name }) => + Boolean(classname) && Boolean(formattedTime) && Boolean(name), + }, + }, + text: { + name: __('Name'), + duration: __('Execution time'), + trace: __('System output'), + }, + modalCloseButton: { + text: __('Close'), + attributes: [{ variant: 'info' }], + }, +}; +</script> + +<template> + <gl-modal + :modal-id="modalId" + :title="testCase.classname" + :action-primary="$options.modalCloseButton" + > + <div class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3"> + <strong class="gl-text-right col-sm-3">{{ $options.text.name }}</strong> + <div class="col-sm-9" data-testid="test-case-name"> + {{ testCase.name }} + </div> + </div> + + <div class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3"> + <strong class="gl-text-right col-sm-3">{{ $options.text.duration }}</strong> + <div class="col-sm-9" data-testid="test-case-duration"> + {{ testCase.formattedTime }} + </div> + </div> + + <div + v-if="testCase.system_output" + class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3" + data-testid="test-case-trace" + > + <strong class="gl-text-right col-sm-3">{{ $options.text.trace }}</strong> + <div class="col-sm-9"> + <code-block :code="testCase.system_output" /> + </div> + </div> + </gl-modal> +</template> 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 c3398e90895..a56dcf48d92 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue @@ -61,7 +61,7 @@ export default { <div v-else-if="!isLoading && showTests" ref="container" - class="tests-detail position-relative" + class="position-relative" data-testid="tests-detail" > <transition @@ -69,13 +69,13 @@ export default { @before-enter="beforeEnterTransition" @after-leave="afterLeaveTransition" > - <div v-if="showSuite" key="detail" class="w-100 position-absolute slide-enter-to-element"> + <div v-if="showSuite" key="detail" class="w-100 slide-enter-to-element"> <test-summary :report="getSelectedSuite" show-back @on-back-click="summaryBackClick" /> <test-suite-table /> </div> - <div v-else key="summary" class="w-100 position-absolute slide-enter-from-element"> + <div v-else key="summary" class="w-100 slide-enter-from-element"> <test-summary :report="testReports" /> <test-summary-table @row-click="summaryTableRowClick" /> 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 2b92ffc3f26..7afbb59cbd6 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,7 +1,8 @@ <script> import { mapGetters } from 'vuex'; -import { GlTooltipDirective, GlFriendlyWrap, GlIcon, GlButton } from '@gitlab/ui'; +import { GlModalDirective, GlTooltipDirective, GlFriendlyWrap, GlIcon, GlButton } from '@gitlab/ui'; import { __ } from '~/locale'; +import TestCaseDetails from './test_case_details.vue'; export default { name: 'TestsSuiteTable', @@ -9,9 +10,11 @@ export default { GlIcon, GlFriendlyWrap, GlButton, + TestCaseDetails, }, directives: { GlTooltip: GlTooltipDirective, + GlModalDirective, }, props: { heading: { @@ -43,7 +46,7 @@ export default { <div role="rowheader" class="table-section section-20"> {{ __('Suite') }} </div> - <div role="rowheader" class="table-section section-20"> + <div role="rowheader" class="table-section section-40"> {{ __('Name') }} </div> <div role="rowheader" class="table-section section-10"> @@ -52,12 +55,12 @@ export default { <div role="rowheader" class="table-section section-10 text-center"> {{ __('Status') }} </div> - <div role="rowheader" class="table-section flex-grow-1"> - {{ __('Trace'), }} - </div> - <div role="rowheader" class="table-section section-10 text-right"> + <div role="rowheader" class="table-section section-10"> {{ __('Duration') }} </div> + <div role="rowheader" class="table-section section-10"> + {{ __('Details'), }} + </div> </div> <div @@ -72,7 +75,7 @@ export default { </div> </div> - <div class="table-section section-20 section-wrap"> + <div class="table-section section-40 section-wrap"> <div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div> <div class="table-mobile-content gl-md-pr-2 gl-overflow-wrap-break"> <gl-friendly-wrap :symbols="$options.wrapSymbols" :text="testCase.name" /> @@ -107,24 +110,24 @@ export default { </div> </div> - <div class="table-section flex-grow-1"> - <div role="rowheader" class="table-mobile-header">{{ __('Trace'), }}</div> - <div class="table-mobile-content"> - <pre - v-if="testCase.system_output" - class="build-trace build-trace-rounded text-left" - ><code class="bash p-0">{{testCase.system_output}}</code></pre> - </div> - </div> - <div class="table-section section-10 section-wrap"> <div role="rowheader" class="table-mobile-header"> {{ __('Duration') }} </div> - <div class="table-mobile-content text-right pr-sm-1"> + <div class="table-mobile-content pr-sm-1"> {{ testCase.formattedTime }} </div> </div> + + <div class="table-section section-10 section-wrap"> + <div role="rowheader" class="table-mobile-header">{{ __('Details'), }}</div> + <div class="table-mobile-content"> + <gl-button v-gl-modal-directive="`test-case-details-${index}`">{{ + __('View details') + }}</gl-button> + <test-case-details :modal-id="`test-case-details-${index}`" :test-case="testCase" /> + </div> + </div> </div> </div> |