summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/pipelines/components
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-11-19 08:27:35 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-11-19 08:27:35 +0000
commit7e9c479f7de77702622631cff2628a9c8dcbc627 (patch)
treec8f718a08e110ad7e1894510980d2155a6549197 /app/assets/javascripts/pipelines/components
parente852b0ae16db4052c1c567d9efa4facc81146e88 (diff)
downloadgitlab-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')
-rw-r--r--app/assets/javascripts/pipelines/components/dag/dag.vue21
-rw-r--r--app/assets/javascripts/pipelines/components/graph/constants.js3
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue73
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_item.vue11
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue27
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue15
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue72
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue28
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_graph/stage_pill.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue9
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/stage.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue1
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_case_details.vue67
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_reports.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue39
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>