Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 15:26:25 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 15:26:25 +0300
commita09983ae35713f5a2bbb100981116d31ce99826e (patch)
tree2ee2af7bd104d57086db360a7e6d8c9d5d43667a /app/assets/javascripts/pipelines
parent18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff)
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'app/assets/javascripts/pipelines')
-rw-r--r--app/assets/javascripts/pipelines/components/dag/constants.js5
-rw-r--r--app/assets/javascripts/pipelines/components/dag/dag.vue117
-rw-r--r--app/assets/javascripts/pipelines/components/dag/dag_annotations.vue73
-rw-r--r--app/assets/javascripts/pipelines/components/dag/dag_graph.vue70
-rw-r--r--app/assets/javascripts/pipelines/components/dag/interactions.js50
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue16
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_item.vue14
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue53
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue (renamed from app/assets/javascripts/pipelines/components/blank_state.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue (renamed from app/assets/javascripts/pipelines/components/empty_state.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue (renamed from app/assets/javascripts/pipelines/components/nav_controls.vue)2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stop_modal.vue (renamed from app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue (renamed from app/assets/javascripts/pipelines/components/pipeline_triggerer.vue)4
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue (renamed from app/assets/javascripts/pipelines/components/pipeline_url.vue)30
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue (renamed from app/assets/javascripts/pipelines/components/pipelines.vue)56
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_actions.vue)2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_artifacts.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_table.vue)8
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_table_row.vue)26
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/stage.vue (renamed from app/assets/javascripts/pipelines/components/stage.vue)14
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue (renamed from app/assets/javascripts/pipelines/components/time_ago.vue)6
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue (renamed from app/assets/javascripts/pipelines/components/tokens/pipeline_branch_name_token.vue)2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue (renamed from app/assets/javascripts/pipelines/components/tokens/pipeline_status_token.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue (renamed from app/assets/javascripts/pipelines/components/tokens/pipeline_tag_name_token.vue)2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue (renamed from app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue)2
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_reports.vue36
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue30
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_summary.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue16
-rw-r--r--app/assets/javascripts/pipelines/constants.js1
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js12
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js56
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/actions.js45
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/getters.js12
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/index.js13
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js4
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/mutations.js12
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/state.js13
42 files changed, 593 insertions, 227 deletions
diff --git a/app/assets/javascripts/pipelines/components/dag/constants.js b/app/assets/javascripts/pipelines/components/dag/constants.js
index 51b1fb4f4cc..b6a98fdc488 100644
--- a/app/assets/javascripts/pipelines/components/dag/constants.js
+++ b/app/assets/javascripts/pipelines/components/dag/constants.js
@@ -8,3 +8,8 @@ export const DEFAULT = 'default';
export const IS_HIGHLIGHTED = 'dag-highlighted';
export const LINK_SELECTOR = 'dag-link';
export const NODE_SELECTOR = 'dag-node';
+
+/* Annotation types */
+export const ADD_NOTE = 'add';
+export const REMOVE_NOTE = 'remove';
+export const REPLACE_NOTES = 'replace';
diff --git a/app/assets/javascripts/pipelines/components/dag/dag.vue b/app/assets/javascripts/pipelines/components/dag/dag.vue
index 6e0d23ef87f..85163a666e2 100644
--- a/app/assets/javascripts/pipelines/components/dag/dag.vue
+++ b/app/assets/javascripts/pipelines/components/dag/dag.vue
@@ -1,19 +1,32 @@
<script>
-import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
+import { GlAlert, GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
+import { isEmpty } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import DagGraph from './dag_graph.vue';
-import { DEFAULT, PARSE_FAILURE, LOAD_FAILURE, UNSUPPORTED_DATA } from './constants';
+import DagAnnotations from './dag_annotations.vue';
+import {
+ DEFAULT,
+ PARSE_FAILURE,
+ LOAD_FAILURE,
+ UNSUPPORTED_DATA,
+ ADD_NOTE,
+ REMOVE_NOTE,
+ REPLACE_NOTES,
+} from './constants';
import { parseData } from './parsing_utils';
export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
name: 'Dag',
components: {
+ DagAnnotations,
DagGraph,
GlAlert,
GlLink,
GlSprintf,
+ GlEmptyState,
+ GlButton,
},
props: {
graphUrl: {
@@ -21,21 +34,43 @@ export default {
required: false,
default: '',
},
+ emptySvgPath: {
+ type: String,
+ required: true,
+ default: '',
+ },
+ dagDocPath: {
+ type: String,
+ required: true,
+ default: '',
+ },
},
data() {
return {
- showFailureAlert: false,
- showBetaInfo: true,
+ annotationsMap: {},
failureType: null,
graphData: null,
+ showFailureAlert: false,
+ showBetaInfo: true,
+ hasNoDependentJobs: false,
};
},
errorTexts: {
[LOAD_FAILURE]: __('We are currently unable to fetch data for this graph.'),
[PARSE_FAILURE]: __('There was an error parsing the data for this graph.'),
- [UNSUPPORTED_DATA]: __('A DAG must have two dependent jobs to be visualized on this tab.'),
+ [UNSUPPORTED_DATA]: __('DAG visualization requires at least 3 dependent jobs.'),
[DEFAULT]: __('An unknown error occurred while loading this graph.'),
},
+ emptyStateTexts: {
+ title: __('Start using Directed Acyclic Graphs (DAG)'),
+ firstDescription: __(
+ "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph.",
+ ),
+ 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.',
+ ),
+ button: __('Learn more about job dependencies'),
+ },
computed: {
betaMessage() {
return __(
@@ -66,6 +101,9 @@ export default {
};
}
},
+ shouldDisplayAnnotations() {
+ return !isEmpty(this.annotationsMap);
+ },
shouldDisplayGraph() {
return Boolean(!this.showFailureAlert && this.graphData);
},
@@ -86,6 +124,9 @@ export default {
.catch(() => reportFailure(LOAD_FAILURE));
},
methods: {
+ addAnnotationToMap({ uid, source, target }) {
+ this.$set(this.annotationsMap, uid, { source, target });
+ },
processGraphData(data) {
let parsed;
@@ -96,11 +137,18 @@ export default {
return;
}
- if (parsed.links.length < 2) {
+ if (parsed.links.length === 1) {
this.reportFailure(UNSUPPORTED_DATA);
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;
+ }
+
this.graphData = parsed;
},
hideAlert() {
@@ -109,10 +157,28 @@ export default {
hideBetaInfo() {
this.showBetaInfo = false;
},
+ removeAnnotationFromMap({ uid }) {
+ this.$delete(this.annotationsMap, uid);
+ },
reportFailure(type) {
this.showFailureAlert = true;
this.failureType = type;
},
+ updateAnnotation({ type, data }) {
+ switch (type) {
+ case ADD_NOTE:
+ this.addAnnotationToMap(data);
+ break;
+ case REMOVE_NOTE:
+ this.removeAnnotationFromMap(data);
+ break;
+ case REPLACE_NOTES:
+ this.annotationsMap = data;
+ break;
+ default:
+ break;
+ }
+ },
},
};
</script>
@@ -131,6 +197,43 @@ export default {
</template>
</gl-sprintf>
</gl-alert>
- <dag-graph v-if="shouldDisplayGraph" :graph-data="graphData" @onFailure="reportFailure" />
+ <div class="gl-relative">
+ <dag-annotations v-if="shouldDisplayAnnotations" :annotations="annotationsMap" />
+ <dag-graph
+ v-if="shouldDisplayGraph"
+ :graph-data="graphData"
+ @onFailure="reportFailure"
+ @update-annotation="updateAnnotation"
+ />
+ <gl-empty-state
+ v-else-if="hasNoDependentJobs"
+ :svg-path="emptySvgPath"
+ :title="$options.emptyStateTexts.title"
+ >
+ <template #description>
+ <div class="gl-text-left">
+ <p>
+ <gl-sprintf :message="$options.emptyStateTexts.firstDescription">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ <p>
+ <gl-sprintf :message="$options.emptyStateTexts.secondDescription">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ </div>
+ </template>
+ <template #actions>
+ <gl-button :href="dagDocPath" target="__blank" variant="success">
+ {{ $options.emptyStateTexts.button }}
+ </gl-button>
+ </template>
+ </gl-empty-state>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/dag/dag_annotations.vue b/app/assets/javascripts/pipelines/components/dag/dag_annotations.vue
new file mode 100644
index 00000000000..a1500166cdc
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/dag/dag_annotations.vue
@@ -0,0 +1,73 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { __ } from '~/locale';
+
+export default {
+ name: 'DagAnnotations',
+ components: {
+ GlButton,
+ },
+ props: {
+ annotations: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ showList: true,
+ };
+ },
+ computed: {
+ linkText() {
+ return this.showList ? __('Hide list') : __('Show list');
+ },
+ shouldShowLink() {
+ return Object.keys(this.annotations).length > 1;
+ },
+ wrapperClasses() {
+ return [
+ 'gl-display-flex',
+ 'gl-flex-direction-column',
+ 'gl-fixed',
+ 'gl-right-1',
+ 'gl-top-66vh',
+ 'gl-w-max-content',
+ 'gl-px-5',
+ 'gl-py-4',
+ 'gl-rounded-base',
+ 'gl-bg-white',
+ ].join(' ');
+ },
+ },
+ methods: {
+ toggleList() {
+ this.showList = !this.showList;
+ },
+ },
+};
+</script>
+<template>
+ <div :class="wrapperClasses">
+ <div v-if="showList">
+ <div
+ v-for="note in annotations"
+ :key="note.uid"
+ class="gl-display-flex gl-align-items-center"
+ >
+ <div
+ data-testid="dag-color-block"
+ class="gl-w-6 gl-h-5"
+ :style="{
+ background: `linear-gradient(0.25turn, ${note.source.color} 40%, ${note.target.color} 60%)`,
+ }"
+ ></div>
+ <div data-testid="dag-note-text" class="gl-px-2 gl-font-base gl-align-items-center">
+ {{ note.source.name }} → {{ note.target.name }}
+ </div>
+ </div>
+ </div>
+
+ <gl-button v-if="shouldShowLink" variant="link" @click="toggleList">{{ linkText }}</gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/dag/dag_graph.vue b/app/assets/javascripts/pipelines/components/dag/dag_graph.vue
index 063ec091e4d..d12baa9617e 100644
--- a/app/assets/javascripts/pipelines/components/dag/dag_graph.vue
+++ b/app/assets/javascripts/pipelines/components/dag/dag_graph.vue
@@ -1,8 +1,17 @@
<script>
import * as d3 from 'd3';
import { uniqueId } from 'lodash';
-import { LINK_SELECTOR, NODE_SELECTOR, PARSE_FAILURE } from './constants';
import {
+ LINK_SELECTOR,
+ NODE_SELECTOR,
+ PARSE_FAILURE,
+ ADD_NOTE,
+ REMOVE_NOTE,
+ REPLACE_NOTES,
+} from './constants';
+import {
+ currentIsLive,
+ getLiveLinksAsDict,
highlightLinks,
restoreLinks,
toggleLinkHighlight,
@@ -25,6 +34,11 @@ export default {
containerClasses: ['dag-graph-container', 'gl-display-flex', 'gl-flex-direction-column'].join(
' ',
),
+ hoverFadeClasses: [
+ 'gl-cursor-pointer',
+ 'gl-transition-duration-slow',
+ 'gl-transition-timing-function-ease',
+ ].join(' '),
},
gitLabColorRotation: [
'#e17223',
@@ -50,8 +64,8 @@ export default {
data() {
return {
color: () => {},
- width: 0,
height: 0,
+ width: 0,
};
},
mounted() {
@@ -60,7 +74,7 @@ export default {
try {
countedAndTransformed = this.transformData(this.graphData);
} catch {
- this.$emit('onFailure', PARSE_FAILURE);
+ this.$emit('on-failure', PARSE_FAILURE);
return;
}
@@ -90,17 +104,33 @@ export default {
},
appendLinkInteractions(link) {
+ const { baseOpacity } = this.$options.viewOptions;
return link
- .on('mouseover', highlightLinks)
- .on('mouseout', restoreLinks.bind(null, this.$options.viewOptions.baseOpacity))
- .on('click', toggleLinkHighlight.bind(null, this.$options.viewOptions.baseOpacity));
+ .on('mouseover', (d, idx, collection) => {
+ if (currentIsLive(idx, collection)) {
+ return;
+ }
+ this.$emit('update-annotation', { type: ADD_NOTE, data: d });
+ highlightLinks(d, idx, collection);
+ })
+ .on('mouseout', (d, idx, collection) => {
+ if (currentIsLive(idx, collection)) {
+ return;
+ }
+ this.$emit('update-annotation', { type: REMOVE_NOTE, data: d });
+ restoreLinks(baseOpacity);
+ })
+ .on('click', (d, idx, collection) => {
+ toggleLinkHighlight(baseOpacity, d, idx, collection);
+ this.$emit('update-annotation', { type: REPLACE_NOTES, data: getLiveLinksAsDict() });
+ });
},
appendNodeInteractions(node) {
- return node.on(
- 'click',
- togglePathHighlights.bind(null, this.$options.viewOptions.baseOpacity),
- );
+ return node.on('click', (d, idx, collection) => {
+ togglePathHighlights(this.$options.viewOptions.baseOpacity, d, idx, collection);
+ this.$emit('update-annotation', { type: REPLACE_NOTES, data: getLiveLinksAsDict() });
+ });
},
appendLabelAsForeignObject(d, i, n) {
@@ -230,7 +260,10 @@ export default {
.attr('id', d => {
return this.createAndAssignId(d, 'uid', LINK_SELECTOR);
})
- .classed(`${LINK_SELECTOR} gl-cursor-pointer`, true);
+ .classed(
+ `${LINK_SELECTOR} gl-transition-property-stroke-opacity ${this.$options.viewOptions.hoverFadeClasses}`,
+ true,
+ );
},
generateNodes(svg, nodeData) {
@@ -242,7 +275,10 @@ export default {
.data(nodeData)
.enter()
.append('line')
- .classed(`${NODE_SELECTOR} gl-cursor-pointer`, true)
+ .classed(
+ `${NODE_SELECTOR} gl-transition-property-stroke ${this.$options.viewOptions.hoverFadeClasses}`,
+ true,
+ )
.attr('id', d => {
return this.createAndAssignId(d, 'uid', NODE_SELECTOR);
})
@@ -260,6 +296,11 @@ export default {
.attr('y2', d => d.y1 - 4);
},
+ initColors() {
+ const colorFn = d3.scaleOrdinal(this.$options.gitLabColorRotation);
+ return ({ name }) => colorFn(name);
+ },
+
labelNodes(svg, nodeData) {
return svg
.append('g')
@@ -271,11 +312,6 @@ export default {
.each(this.appendLabelAsForeignObject);
},
- initColors() {
- const colorFn = d3.scaleOrdinal(this.$options.gitLabColorRotation);
- return ({ name }) => colorFn(name);
- },
-
transformData(parsed) {
const baseLayout = createSankey()(parsed);
const cleanedNodes = removeOrphanNodes(baseLayout.nodes);
diff --git a/app/assets/javascripts/pipelines/components/dag/interactions.js b/app/assets/javascripts/pipelines/components/dag/interactions.js
index c9008730c90..e9f3e9f0e2c 100644
--- a/app/assets/javascripts/pipelines/components/dag/interactions.js
+++ b/app/assets/javascripts/pipelines/components/dag/interactions.js
@@ -5,10 +5,20 @@ export const highlightIn = 1;
export const highlightOut = 0.2;
const getCurrent = (idx, collection) => d3.select(collection[idx]);
-const currentIsLive = (idx, collection) => getCurrent(idx, collection).classed(IS_HIGHLIGHTED);
+const getLiveLinks = () => d3.selectAll(`.${LINK_SELECTOR}.${IS_HIGHLIGHTED}`);
const getOtherLinks = () => d3.selectAll(`.${LINK_SELECTOR}:not(.${IS_HIGHLIGHTED})`);
const getNodesNotLive = () => d3.selectAll(`.${NODE_SELECTOR}:not(.${IS_HIGHLIGHTED})`);
+export const getLiveLinksAsDict = () => {
+ return Object.fromEntries(
+ getLiveLinks()
+ .data()
+ .map(d => [d.uid, d]),
+ );
+};
+export const currentIsLive = (idx, collection) =>
+ getCurrent(idx, collection).classed(IS_HIGHLIGHTED);
+
const backgroundLinks = selection => selection.style('stroke-opacity', highlightOut);
const backgroundNodes = selection => selection.attr('stroke', '#f2f2f2');
const foregroundLinks = selection => selection.style('stroke-opacity', highlightIn);
@@ -16,10 +26,10 @@ const foregroundNodes = selection => selection.attr('stroke', d => d.color);
const renewLinks = (selection, baseOpacity) => selection.style('stroke-opacity', baseOpacity);
const renewNodes = selection => selection.attr('stroke', d => d.color);
-const getAllLinkAncestors = node => {
+export const getAllLinkAncestors = node => {
if (node.targetLinks) {
return node.targetLinks.flatMap(n => {
- return [n.uid, ...getAllLinkAncestors(n.source)];
+ return [n, ...getAllLinkAncestors(n.source)];
});
}
@@ -59,8 +69,8 @@ const highlightPath = (parentLinks, parentNodes) => {
backgroundNodes(getNodesNotLive());
/* highlight correct links */
- parentLinks.forEach(id => {
- foregroundLinks(d3.select(`#${id}`)).classed(IS_HIGHLIGHTED, true);
+ parentLinks.forEach(({ uid }) => {
+ foregroundLinks(d3.select(`#${uid}`)).classed(IS_HIGHLIGHTED, true);
});
/* highlight correct nodes */
@@ -69,9 +79,22 @@ const highlightPath = (parentLinks, parentNodes) => {
});
};
+const restoreNodes = () => {
+ /*
+ When paths are unclicked, they can take down nodes that
+ are still in use for other paths. This checks the live paths and
+ rehighlights their nodes.
+ */
+
+ getLiveLinks().each(d => {
+ foregroundNodes(d3.select(`#${d.source.uid}`)).classed(IS_HIGHLIGHTED, true);
+ foregroundNodes(d3.select(`#${d.target.uid}`)).classed(IS_HIGHLIGHTED, true);
+ });
+};
+
const restorePath = (parentLinks, parentNodes, baseOpacity) => {
- parentLinks.forEach(id => {
- renewLinks(d3.select(`#${id}`), baseOpacity).classed(IS_HIGHLIGHTED, false);
+ parentLinks.forEach(({ uid }) => {
+ renewLinks(d3.select(`#${uid}`), baseOpacity).classed(IS_HIGHLIGHTED, false);
});
parentNodes.forEach(id => {
@@ -86,14 +109,10 @@ const restorePath = (parentLinks, parentNodes, baseOpacity) => {
backgroundLinks(getOtherLinks());
backgroundNodes(getNodesNotLive());
+ restoreNodes();
};
-export const restoreLinks = (baseOpacity, d, idx, collection) => {
- /* in this case, it has just been clicked */
- if (currentIsLive(idx, collection)) {
- return;
- }
-
+export const restoreLinks = baseOpacity => {
/*
if there exist live links, reset to highlight out / pale
otherwise, reset to base
@@ -111,11 +130,12 @@ export const restoreLinks = (baseOpacity, d, idx, collection) => {
export const toggleLinkHighlight = (baseOpacity, d, idx, collection) => {
if (currentIsLive(idx, collection)) {
- restorePath([d.uid], [d.source.uid, d.target.uid], baseOpacity);
+ restorePath([d], [d.source.uid, d.target.uid], baseOpacity);
+ restoreNodes();
return;
}
- highlightPath([d.uid], [d.source.uid, d.target.uid]);
+ highlightPath([d], [d.source.uid, d.target.uid]);
};
export const togglePathHighlights = (baseOpacity, d, idx, collection) => {
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 1ff5b662d18..6b890688a48 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -43,6 +43,7 @@ export default {
data() {
return {
downstreamMarginTop: null,
+ jobName: null,
};
},
computed: {
@@ -91,13 +92,9 @@ export default {
/**
* Calculates the margin top of the clicked downstream pipeline by
* subtracting the clicked downstream pipelines offsetTop by it's parent's
- * offsetTop and then subtracting either 15 (if child) or 30 (if not a child)
- * due to the height of node and stage name margin bottom.
+ * offsetTop and then subtracting 15
*/
- this.downstreamMarginTop = this.calculateMarginTop(
- downstreamNode,
- downstreamNode.classList.contains('child-pipeline') ? 15 : 30,
- );
+ this.downstreamMarginTop = this.calculateMarginTop(downstreamNode, 15);
/**
* If the expanded trigger is defined and the id is different than the
@@ -120,6 +117,9 @@ export default {
hasUpstream(index) {
return index === 0 && this.hasTriggeredBy;
},
+ setJob(jobName) {
+ this.jobName = jobName;
+ },
},
};
</script>
@@ -172,7 +172,7 @@ export default {
:class="{
'has-upstream prepend-left-64': hasUpstream(index),
'has-only-one-job': hasOnlyOneJob(stage),
- 'append-right-46': shouldAddRightMargin(index),
+ 'gl-mr-26': shouldAddRightMargin(index),
}"
:title="capitalizeStageName(stage.name)"
:groups="stage.groups"
@@ -180,6 +180,7 @@ export default {
:is-first-column="isFirstColumn(index)"
:has-triggered-by="hasTriggeredBy"
:action="stage.status.action"
+ :job-hovered="jobName"
@refreshPipelineGraph="refreshPipelineGraph"
/>
</ul>
@@ -191,6 +192,7 @@ export default {
:project-id="pipelineProjectId"
graph-position="right"
@linkedPipelineClick="handleClickedDownstream"
+ @downstreamHovered="setJob"
/>
<pipeline-graph
diff --git a/app/assets/javascripts/pipelines/components/graph/job_item.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue
index bfd314e0439..4d72cc55b34 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_item.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue
@@ -31,6 +31,7 @@ import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
*/
export default {
+ hoverClass: 'gl-inset-border-1-blue-500',
components: {
ActionComponent,
JobNameComponent,
@@ -55,6 +56,11 @@ export default {
required: false,
default: Infinity,
},
+ jobHovered: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
boundary() {
@@ -95,6 +101,11 @@ export default {
hasAction() {
return this.job.status && this.job.status.action && this.job.status.action.path;
},
+ jobClasses() {
+ return this.job.name === this.jobHovered
+ ? `${this.$options.hoverClass} ${this.cssClassJobName}`
+ : this.cssClassJobName;
+ },
},
methods: {
pipelineActionRequestComplete() {
@@ -120,8 +131,9 @@ export default {
v-else
v-gl-tooltip="{ boundary, placement: 'bottom' }"
:title="tooltipText"
- :class="cssClassJobName"
+ :class="jobClasses"
class="js-job-component-tooltip non-details-job-component"
+ data-testid="job-without-link"
>
<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 550b9daa521..733553e02c0 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
@@ -1,7 +1,7 @@
<script>
import { GlLoadingIcon, GlTooltipDirective, GlDeprecatedButton } from '@gitlab/ui';
import CiStatus from '~/vue_shared/components/ci_icon.vue';
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
export default {
directives: {
@@ -28,7 +28,8 @@ export default {
},
computed: {
tooltipText() {
- return `${this.projectName} - ${this.pipelineStatus.label}`;
+ return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label}
+ ${this.sourceJobInfo}`;
},
buttonId() {
return `js-linked-pipeline-${this.pipeline.id}`;
@@ -39,25 +40,32 @@ export default {
projectName() {
return this.pipeline.project.name;
},
+ downstreamTitle() {
+ 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');
},
childPipeline() {
// Refactor string match when BE returns Upstream/Downstream indicators
- return this.projectId === this.pipeline.project.id && this.columnTitle === __('Downstream');
+ return this.projectId === this.pipeline.project.id && this.isDownstream;
},
label() {
- return this.parentPipeline ? __('Parent') : __('Child');
- },
- childTooltipText() {
- return __('This pipeline was triggered by a parent pipeline');
+ if (this.parentPipeline) {
+ return __('Parent');
+ } else if (this.childPipeline) {
+ return __('Child');
+ }
+ return __('Multi-project');
},
- parentTooltipText() {
- return __('This pipeline triggered a child pipeline');
+ isDownstream() {
+ return this.columnTitle === __('Downstream');
},
- labelToolTipText() {
- return this.label === __('Parent') ? this.parentTooltipText : this.childTooltipText;
+ sourceJobInfo() {
+ return this.isDownstream
+ ? sprintf(__('Created by %{job}'), { job: this.pipeline.source_job.name })
+ : '';
},
},
methods: {
@@ -68,6 +76,12 @@ export default {
hideTooltips() {
this.$root.$emit('bv::hide::tooltip');
},
+ onDownstreamHovered() {
+ this.$emit('downstreamHovered', this.pipeline.source_job.name);
+ },
+ onDownstreamHoverLeave() {
+ this.$emit('downstreamHovered', '');
+ },
},
};
</script>
@@ -76,7 +90,10 @@ export default {
<li
ref="linkedPipeline"
class="linked-pipeline build"
- :class="{ 'child-pipeline': childPipeline }"
+ :class="{ 'downstream-pipeline': isDownstream }"
+ data-qa-selector="child_pipeline"
+ @mouseover="onDownstreamHovered"
+ @mouseleave="onDownstreamHoverLeave"
>
<gl-deprecated-button
:id="buttonId"
@@ -94,15 +111,9 @@ export default {
css-classes="position-top-0"
class="js-linked-pipeline-status"
/>
- <span class="str-truncated align-bottom"> {{ projectName }} &#8226; #{{ pipeline.id }} </span>
- <div v-if="parentPipeline || childPipeline" class="parent-child-label-container">
- <span
- v-gl-tooltip.bottom
- :title="labelToolTipText"
- class="badge badge-primary"
- @mouseover="hideTooltips"
- >{{ label }}</span
- >
+ <span class="str-truncated"> {{ downstreamTitle }} &#8226; #{{ pipeline.id }} </span>
+ <div class="gl-pt-2">
+ <span class="badge badge-primary" data-testid="downstream-pipeline-label">{{ label }}</span>
</div>
</gl-deprecated-button>
</li>
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 e3429184c05..c4dfd3382a2 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
@@ -28,7 +28,7 @@ export default {
columnClass() {
const positionValues = {
right: 'prepend-left-64',
- left: 'append-right-32',
+ left: 'gl-mr-7',
};
return `graph-position-${this.graphPosition} ${positionValues[this.graphPosition]}`;
},
@@ -41,6 +41,9 @@ export default {
onPipelineClick(downstreamNode, pipeline, index) {
this.$emit('linkedPipelineClick', pipeline, index, downstreamNode);
},
+ onDownstreamHovered(jobName) {
+ this.$emit('downstreamHovered', jobName);
+ },
},
};
</script>
@@ -61,6 +64,7 @@ export default {
:column-title="columnTitle"
:project-id="projectId"
@pipelineClicked="onPipelineClick($event, pipeline, index)"
+ @downstreamHovered="onDownstreamHovered"
/>
</ul>
</div>
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index bed0ed51d5f..9de6ba819c2 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -36,6 +36,11 @@ export default {
required: false,
default: () => ({}),
},
+ jobHovered: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
hasAction() {
@@ -80,6 +85,7 @@ export default {
<job-item
v-if="group.size === 1"
:job="group.jobs[0]"
+ :job-hovered="jobHovered"
css-class-job-name="build-content"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index e7777d0d3af..dff642161db 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -108,7 +108,7 @@ export default {
/>
</ci-header>
- <gl-loading-icon v-if="isLoading" size="lg" class="prepend-top-default append-bottom-default" />
+ <gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-3 gl-mb-3" />
<gl-modal
:modal-id="$options.DELETE_MODAL_ID"
diff --git a/app/assets/javascripts/pipelines/components/blank_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue
index 6c3a4a27606..6c3a4a27606 100644
--- a/app/assets/javascripts/pipelines/components/blank_state.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/blank_state.vue
diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
index 74ada6a4d15..74ada6a4d15 100644
--- a/app/assets/javascripts/pipelines/components/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
diff --git a/app/assets/javascripts/pipelines/components/nav_controls.vue b/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue
index 4f6c9d2bd90..a66bbb7e5ba 100644
--- a/app/assets/javascripts/pipelines/components/nav_controls.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue
@@ -1,6 +1,6 @@
<script>
import { GlDeprecatedButton } from '@gitlab/ui';
-import LoadingButton from '../../vue_shared/components/loading_button.vue';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
export default {
name: 'PipelineNavControls',
diff --git a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stop_modal.vue
index f604edd8859..f604edd8859 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_stop_modal.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stop_modal.vue
diff --git a/app/assets/javascripts/pipelines/components/pipeline_triggerer.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
index 740b54cd8e0..35fd9837b3e 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_triggerer.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
@@ -26,9 +26,9 @@ export default {
:img-src="user.avatar_url"
:img-size="26"
:tooltip-text="user.name"
- class="prepend-left-default js-pipeline-url-user"
+ class="gl-ml-3 js-pipeline-url-user"
/>
- <span v-else class="prepend-left-default js-pipeline-url-api api">
+ <span v-else class="gl-ml-3 js-pipeline-url-api api">
{{ s__('Pipelines|API') }}
</span>
</div>
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
index 6c977b841af..2905b2ca26f 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
@@ -1,6 +1,7 @@
<script>
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import { escape } from 'lodash';
+import { SCHEDULE_ORIGIN } from '../../constants';
import { __, sprintf } from '~/locale';
import popover from '~/vue_shared/directives/popover';
@@ -27,6 +28,10 @@ export default {
type: Object,
required: true,
},
+ pipelineScheduleUrl: {
+ type: String,
+ required: true,
+ },
autoDevopsHelpPath: {
type: String,
required: true,
@@ -36,6 +41,9 @@ export default {
user() {
return this.pipeline.user;
},
+ isScheduled() {
+ return this.pipeline.source === SCHEDULE_ORIGIN;
+ },
popoverOptions() {
return {
html: true,
@@ -61,16 +69,28 @@ export default {
<gl-link
:href="pipeline.path"
class="js-pipeline-url-link js-onboarding-pipeline-item"
+ data-testid="pipeline-url-link"
data-qa-selector="pipeline_url_link"
>
<span class="pipeline-id">#{{ pipeline.id }}</span>
</gl-link>
<div class="label-container">
+ <gl-link v-if="isScheduled" :href="pipelineScheduleUrl" target="__blank">
+ <span
+ v-gl-tooltip
+ :title="__('This pipeline was triggered by a schedule.')"
+ class="badge badge-info"
+ data-testid="pipeline-url-scheduled"
+ >
+ {{ __('Scheduled') }}
+ </span>
+ </gl-link>
<span
v-if="pipeline.flags.latest"
v-gl-tooltip
:title="__('Latest pipeline for the most recent commit on this branch')"
class="js-pipeline-url-latest badge badge-success"
+ data-testid="pipeline-url-latest"
>
{{ __('latest') }}
</span>
@@ -79,6 +99,7 @@ export default {
v-gl-tooltip
:title="pipeline.yaml_errors"
class="js-pipeline-url-yaml badge badge-danger"
+ data-testid="pipeline-url-yaml"
>
{{ __('yaml invalid') }}
</span>
@@ -87,6 +108,7 @@ export default {
v-gl-tooltip
:title="pipeline.failure_reason"
class="js-pipeline-url-failure badge badge-danger"
+ data-testid="pipeline-url-failure"
>
{{ __('error') }}
</span>
@@ -95,10 +117,15 @@ export default {
v-popover="popoverOptions"
tabindex="0"
class="js-pipeline-url-autodevops badge badge-info autodevops-badge"
+ data-testid="pipeline-url-autodevops"
role="button"
>{{ __('Auto DevOps') }}</gl-link
>
- <span v-if="pipeline.flags.stuck" class="js-pipeline-url-stuck badge badge-warning">
+ <span
+ v-if="pipeline.flags.stuck"
+ class="js-pipeline-url-stuck badge badge-warning"
+ data-testid="pipeline-url-stuck"
+ >
{{ __('stuck') }}
</span>
<span
@@ -110,6 +137,7 @@ export default {
)
"
class="js-pipeline-url-detached badge badge-info"
+ data-testid="pipeline-url-detached"
>
{{ __('detached') }}
</span>
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
index dbf29b0c29c..0c531650fd2 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
@@ -1,17 +1,18 @@
<script>
import { isEqual } from 'lodash';
-import { __, sprintf, s__ } from '../../locale';
-import createFlash from '../../flash';
-import PipelinesService from '../services/pipelines_service';
-import pipelinesMixin from '../mixins/pipelines';
-import TablePagination from '../../vue_shared/components/pagination/table_pagination.vue';
-import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue';
+import { __, s__ } from '~/locale';
+import createFlash from '~/flash';
+import PipelinesService from '../../services/pipelines_service';
+import pipelinesMixin from '../../mixins/pipelines';
+import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
+import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import NavigationControls from './nav_controls.vue';
-import { getParameterByName } from '../../lib/utils/common_utils';
-import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
+import { getParameterByName } from '~/lib/utils/common_utils';
+import CIPaginationMixin from '~/vue_shared/mixins/ci_pagination_api_mixin';
+import Icon from '~/vue_shared/components/icon.vue';
import PipelinesFilteredSearch from './pipelines_filtered_search.vue';
-import { validateParams } from '../utils';
-import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING, FILTER_TAG_IDENTIFIER } from '../constants';
+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 {
@@ -20,6 +21,7 @@ export default {
NavigationTabs,
NavigationControls,
PipelinesFilteredSearch,
+ Icon,
},
mixins: [pipelinesMixin, CIPaginationMixin, glFeatureFlagsMixin()],
props: {
@@ -40,6 +42,11 @@ export default {
type: String,
required: true,
},
+ pipelineScheduleUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
helpPagePath: {
type: String,
required: true,
@@ -115,8 +122,6 @@ export default {
},
scopes: {
all: 'all',
- pending: 'pending',
- running: 'running',
finished: 'finished',
branches: 'branches',
tags: 'tags',
@@ -169,13 +174,8 @@ export default {
},
emptyTabMessage() {
- const { scopes } = this.$options;
- const possibleScopes = [scopes.pending, scopes.running, scopes.finished];
-
- if (possibleScopes.includes(this.scope)) {
- return sprintf(s__('Pipelines|There are currently no %{scope} pipelines.'), {
- scope: this.scope,
- });
+ if (this.scope === this.$options.scopes.finished) {
+ return s__('Pipelines|There are currently no finished pipelines.');
}
return s__('Pipelines|There are currently no pipelines.');
@@ -193,21 +193,8 @@ export default {
isActive: this.scope === 'all',
},
{
- name: __('Pending'),
- scope: scopes.pending,
- count: count.pending,
- isActive: this.scope === 'pending',
- },
- {
- name: __('Running'),
- scope: scopes.running,
- count: count.running,
- isActive: this.scope === 'running',
- },
- {
name: __('Finished'),
scope: scopes.finished,
- count: count.finished,
isActive: this.scope === 'finished',
},
{
@@ -298,8 +285,8 @@ export default {
v-if="shouldRenderTabs || shouldRenderButtons"
class="top-area scrolling-tabs-container inner-page-scroll-tabs"
>
- <div class="fade-left"><i class="fa fa-angle-left" aria-hidden="true"> </i></div>
- <div class="fade-right"><i class="fa fa-angle-right" aria-hidden="true"> </i></div>
+ <div class="fade-left"><icon name="chevron-lg-left" :size="12" /></div>
+ <div class="fade-right"><icon name="chevron-lg-right" :size="12" /></div>
<navigation-tabs
v-if="shouldRenderTabs"
@@ -358,6 +345,7 @@ export default {
<div v-else-if="stateToRender === $options.stateMap.tableList" class="table-holder">
<pipelines-table-component
:pipelines="state.pipelines"
+ :pipeline-schedule-url="pipelineScheduleUrl"
:update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsPath"
:view-type="viewType"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue
index 7d4276e8d2e..3009ca7a775 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue
@@ -5,7 +5,7 @@ import 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';
-import eventHub from '../event_hub';
+import eventHub from '../../event_hub';
export default {
directives: {
diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue
index 59c066b2683..59c066b2683 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue
diff --git a/app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue
index 0505a8668d1..0505a8668d1 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
index d3ba0c97f6b..b8112149778 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -2,7 +2,7 @@
import { GlTooltipDirective } from '@gitlab/ui';
import PipelinesTableRowComponent from './pipelines_table_row.vue';
import PipelineStopModal from './pipeline_stop_modal.vue';
-import eventHub from '../event_hub';
+import eventHub from '../../event_hub';
/**
* Pipelines Table Component.
@@ -22,6 +22,11 @@ export default {
type: Array,
required: true,
},
+ pipelineScheduleUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
updateGraphDropdown: {
type: Boolean,
required: false,
@@ -91,6 +96,7 @@ export default {
v-for="model in pipelines"
:key="model.id"
:pipeline="model"
+ :pipeline-schedule-url="pipelineScheduleUrl"
:update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
index 981914dd046..f25994a7506 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
@@ -1,16 +1,16 @@
<script>
-import eventHub from '../event_hub';
+import eventHub from '../../event_hub';
import PipelinesActionsComponent from './pipelines_actions.vue';
import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
-import CiBadge from '../../vue_shared/components/ci_badge_link.vue';
+import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
import PipelineStage from './stage.vue';
import PipelineUrl from './pipeline_url.vue';
import PipelineTriggerer from './pipeline_triggerer.vue';
import PipelinesTimeago from './time_ago.vue';
-import CommitComponent from '../../vue_shared/components/commit.vue';
-import LoadingButton from '../../vue_shared/components/loading_button.vue';
-import Icon from '../../vue_shared/components/icon.vue';
-import { PIPELINES_TABLE } from '../constants';
+import CommitComponent from '~/vue_shared/components/commit.vue';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import { PIPELINES_TABLE } from '../../constants';
/**
* Pipeline table row.
@@ -35,6 +35,11 @@ export default {
type: Object,
required: true,
},
+ pipelineScheduleUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
updateGraphDropdown: {
type: Boolean,
required: false,
@@ -274,7 +279,11 @@ export default {
</div>
</div>
- <pipeline-url :pipeline="pipeline" :auto-devops-help-path="autoDevopsHelpPath" />
+ <pipeline-url
+ :pipeline="pipeline"
+ :pipeline-schedule-url="pipelineScheduleUrl"
+ :auto-devops-help-path="autoDevopsHelpPath"
+ />
<pipeline-triggerer :pipeline="pipeline" />
<div class="table-section section-wrap section-20">
@@ -300,7 +309,8 @@ export default {
<div
v-for="(stage, index) in pipeline.details.stages"
:key="index"
- class="stage-container dropdown js-mini-pipeline-graph"
+ class="stage-container dropdown"
+ data-testid="widget-mini-pipeline-graph"
>
<pipeline-stage
:type="$options.pipelinesTable"
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
index 569920a4f31..99492bd8357 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/stage.vue
@@ -14,13 +14,13 @@
import $ from 'jquery';
import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
-import { __ } from '../../locale';
-import Flash from '../../flash';
-import axios from '../../lib/utils/axios_utils';
-import eventHub from '../event_hub';
-import Icon from '../../vue_shared/components/icon.vue';
-import JobItem from './graph/job_item.vue';
-import { PIPELINES_TABLE } from '../constants';
+import { __ } from '~/locale';
+import Flash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import eventHub from '../../event_hub';
+import Icon from '~/vue_shared/components/icon.vue';
+import JobItem from '../graph/job_item.vue';
+import { PIPELINES_TABLE } from '../../constants';
export default {
components: {
diff --git a/app/assets/javascripts/pipelines/components/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
index 2a23a0f6744..8a01e1fe3f5 100644
--- a/app/assets/javascripts/pipelines/components/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
@@ -1,8 +1,8 @@
<script>
import iconTimerSvg from 'icons/_icon_timer.svg';
-import '../../lib/utils/datetime_utility';
-import tooltip from '../../vue_shared/directives/tooltip';
-import timeagoMixin from '../../vue_shared/mixins/timeago';
+import '~/lib/utils/datetime_utility';
+import tooltip from '~/vue_shared/directives/tooltip';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
export default {
directives: {
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_branch_name_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue
index da14bb2d308..b6eff2931d3 100644
--- a/app/assets/javascripts/pipelines/components/tokens/pipeline_branch_name_token.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue
@@ -1,7 +1,7 @@
<script>
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import Api from '~/api';
-import { FETCH_BRANCH_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../constants';
+import { FETCH_BRANCH_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../../constants';
import createFlash from '~/flash';
import { debounce } from 'lodash';
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_status_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue
index dc43d94f4fd..dc43d94f4fd 100644
--- a/app/assets/javascripts/pipelines/components/tokens/pipeline_status_token.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_tag_name_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue
index 7b209c5fa12..64de6d2a053 100644
--- a/app/assets/javascripts/pipelines/components/tokens/pipeline_tag_name_token.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue
@@ -1,7 +1,7 @@
<script>
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
import Api from '~/api';
-import { FETCH_TAG_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../constants';
+import { FETCH_TAG_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../../constants';
import createFlash from '~/flash';
import { debounce } from 'lodash';
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue
index 4062a3b11bb..b5aeb3fe9e0 100644
--- a/app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue
@@ -13,7 +13,7 @@ import {
ANY_TRIGGER_AUTHOR,
FETCH_AUTHOR_ERROR_MESSAGE,
FILTER_PIPELINES_SEARCH_DELAY,
-} from '../../constants';
+} from '../../../constants';
export default {
anyTriggerAuthor: ANY_TRIGGER_AUTHOR,
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 06ab45adf80..8746784aa57 100644
--- a/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_reports.vue
@@ -1,10 +1,9 @@
<script>
-import { mapActions, mapState } from 'vuex';
+import { mapActions, mapGetters, mapState } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import TestSuiteTable from './test_suite_table.vue';
import TestSummary from './test_summary.vue';
import TestSummaryTable from './test_summary_table.vue';
-import store from '~/pipelines/stores/test_reports';
export default {
name: 'TestReports',
@@ -14,24 +13,37 @@ export default {
TestSummary,
TestSummaryTable,
},
- store,
computed: {
- ...mapState(['isLoading', 'selectedSuite', 'testReports']),
+ ...mapState(['hasFullReport', 'isLoading', 'selectedSuiteIndex', 'testReports']),
+ ...mapGetters(['getSelectedSuite']),
showSuite() {
- return this.selectedSuite.total_count > 0;
+ return this.selectedSuiteIndex !== null;
},
showTests() {
const { test_suites: testSuites = [] } = this.testReports;
return testSuites.length > 0;
},
},
+ created() {
+ this.fetchSummary();
+ },
methods: {
- ...mapActions(['setSelectedSuite', 'removeSelectedSuite']),
+ ...mapActions([
+ 'fetchFullReport',
+ 'fetchSummary',
+ 'setSelectedSuiteIndex',
+ 'removeSelectedSuiteIndex',
+ ]),
summaryBackClick() {
- this.removeSelectedSuite();
+ this.removeSelectedSuiteIndex();
},
- summaryTableRowClick(suite) {
- this.setSelectedSuite(suite);
+ summaryTableRowClick(index) {
+ this.setSelectedSuiteIndex(index);
+
+ // Fetch the full report when the user clicks to see more details
+ if (!this.hasFullReport) {
+ this.fetchFullReport();
+ }
},
beforeEnterTransition() {
document.documentElement.style.overflowX = 'hidden';
@@ -45,7 +57,7 @@ export default {
<template>
<div v-if="isLoading">
- <gl-loading-icon size="lg" class="prepend-top-default js-loading-spinner" />
+ <gl-loading-icon size="lg" class="gl-mt-3 js-loading-spinner" />
</div>
<div
@@ -59,7 +71,7 @@ export default {
@after-leave="afterLeaveTransition"
>
<div v-if="showSuite" key="detail" class="w-100 position-absolute slide-enter-to-element">
- <test-summary :report="selectedSuite" show-back @on-back-click="summaryBackClick" />
+ <test-summary :report="getSelectedSuite" show-back @on-back-click="summaryBackClick" />
<test-suite-table />
</div>
@@ -73,7 +85,7 @@ export default {
</div>
<div v-else>
- <div class="row prepend-top-default">
+ <div class="row gl-mt-3">
<div class="col-12">
<p class="js-no-tests-to-show">{{ s__('TestReports|There are no tests to show.') }}</p>
</div>
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 be7f27f210d..d57b1466177 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 Icon from '~/vue_shared/components/icon.vue';
-import store from '~/pipelines/stores/test_reports';
import { __ } from '~/locale';
+import { GlTooltipDirective } from '@gitlab/ui';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default {
@@ -11,7 +11,9 @@ export default {
Icon,
SmartVirtualList,
},
- store,
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
props: {
heading: {
type: String,
@@ -32,16 +34,16 @@ export default {
<template>
<div>
- <div class="row prepend-top-default">
+ <div class="row gl-mt-3">
<div class="col-12">
<h4>{{ heading }}</h4>
</div>
</div>
- <div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-cases-table">
+ <div v-if="hasSuites" class="test-reports-table gl-mb-3 js-test-cases-table">
<div role="row" class="gl-responsive-table-row table-row-header font-weight-bold fgray">
<div role="rowheader" class="table-section section-20">
- {{ __('Class') }}
+ {{ __('Suite') }}
</div>
<div role="rowheader" class="table-section section-20">
{{ __('Name') }}
@@ -68,13 +70,25 @@ export default {
class="gl-responsive-table-row rounded align-items-md-start mt-xs-3 js-case-row"
>
<div class="table-section section-20 section-wrap">
- <div role="rowheader" class="table-mobile-header">{{ __('Class') }}</div>
- <div class="table-mobile-content pr-md-1 text-truncate">{{ testCase.classname }}</div>
+ <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>
</div>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div>
- <div class="table-mobile-content pr-md-1 text-truncate">{{ testCase.name }}</div>
+ <div
+ v-gl-tooltip
+ :title="testCase.name"
+ class="table-mobile-content pr-md-1 text-truncate"
+ >
+ {{ testCase.name }}
+ </div>
</div>
<div class="table-section section-10 section-wrap">
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue
index 67646c537bd..712ac5eb0e5 100644
--- a/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary.vue
@@ -72,7 +72,7 @@ export default {
<gl-deprecated-button
v-if="showBack"
size="sm"
- class="append-right-default js-back-button"
+ class="gl-mr-3 js-back-button"
@click="onBackClick"
>
<icon name="angle-left" />
@@ -85,7 +85,7 @@ export default {
<div class="row mt-2">
<div class="col-4 col-md">
<span class="js-total-tests">{{
- sprintf(s__('TestReports|%{count} jobs'), { count: report.total_count })
+ sprintf(s__('TestReports|%{count} tests'), { count: report.total_count })
}}</span>
</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 4dfb67dd8e8..6cfb795595d 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
@@ -2,7 +2,6 @@
import { mapGetters } from 'vuex';
import { s__ } from '~/locale';
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
-import store from '~/pipelines/stores/test_reports';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default {
@@ -14,12 +13,11 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- store,
props: {
heading: {
type: String,
required: false,
- default: s__('TestReports|Test suites'),
+ default: s__('TestReports|Jobs'),
},
},
computed: {
@@ -29,8 +27,8 @@ export default {
},
},
methods: {
- tableRowClick(suite) {
- this.$emit('row-click', suite);
+ tableRowClick(index) {
+ this.$emit('row-click', index);
},
},
maxShownRows: 20,
@@ -40,16 +38,16 @@ export default {
<template>
<div>
- <div class="row prepend-top-default">
+ <div class="row gl-mt-3">
<div class="col-12">
<h4>{{ heading }}</h4>
</div>
</div>
- <div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-suites-table">
+ <div v-if="hasSuites" class="test-reports-table gl-mb-3 js-test-suites-table">
<div role="row" class="gl-responsive-table-row table-row-header font-weight-bold">
<div role="rowheader" class="table-section section-25 pl-3">
- {{ __('Suite') }}
+ {{ __('Job') }}
</div>
<div role="rowheader" class="table-section section-25">
{{ __('Duration') }}
@@ -84,7 +82,7 @@ export default {
:class="{
'gl-responsive-table-row-clickable cursor-pointer': !testSuite.suite_error,
}"
- @click="tableRowClick(testSuite)"
+ @click="tableRowClick(index)"
>
<div class="table-section section-25">
<div role="rowheader" class="table-mobile-header font-weight-bold">
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index c709f329728..abe5e1060c8 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -7,6 +7,7 @@ export const FILTER_PIPELINES_SEARCH_DELAY = 200;
export const ANY_TRIGGER_AUTHOR = 'Any';
export const SUPPORTED_FILTER_PARAMETERS = ['username', 'ref', 'status'];
export const FILTER_TAG_IDENTIFIER = 'tag';
+export const SCHEDULE_ORIGIN = 'schedule';
export const TestStatus = {
FAILED: 'failed',
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 876b30299fb..7710a96e5fb 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -1,11 +1,11 @@
import Visibility from 'visibilityjs';
import { GlLoadingIcon } from '@gitlab/ui';
-import { __ } from '../../locale';
-import createFlash from '../../flash';
-import Poll from '../../lib/utils/poll';
-import EmptyState from '../components/empty_state.vue';
-import SvgBlankState from '../components/blank_state.vue';
-import PipelinesTableComponent from '../components/pipelines_table.vue';
+import { __ } from '~/locale';
+import 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';
+import PipelinesTableComponent from '../components/pipelines_list/pipelines_table.vue';
import eventHub from '../event_hub';
import { CANCEL_REQUEST } from '../constants';
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index 90109598542..f1102a9bddf 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -10,8 +10,7 @@ import PipelinesMediator from './pipeline_details_mediator';
import pipelineHeader from './components/header_component.vue';
import eventHub from './event_hub';
import TestReports from './components/test_reports/test_reports.vue';
-import testReportsStore from './stores/test_reports';
-import axios from '~/lib/utils/axios_utils';
+import createTestReportsStore from './stores/test_reports';
Vue.use(Translate);
@@ -93,15 +92,11 @@ const createPipelineHeaderApp = mediator => {
});
};
-const createPipelinesTabs = dataset => {
+const createPipelinesTabs = testReportsStore => {
const tabsElement = document.querySelector('.pipelines-tabs');
- const testReportsEnabled =
- window.gon && window.gon.features && window.gon.features.junitPipelineView;
-
- if (tabsElement && testReportsEnabled) {
- const fetchReportsAction = 'fetchReports';
- testReportsStore.dispatch('setEndpoint', dataset.testReportEndpoint);
+ if (tabsElement) {
+ const fetchReportsAction = 'fetchFullReport';
const isTestTabActive = Boolean(
document.querySelector('.pipelines-tabs > li > a.test-tab.active'),
);
@@ -121,28 +116,35 @@ const createPipelinesTabs = dataset => {
}
};
-const createTestDetails = detailsEndpoint => {
+const createTestDetails = () => {
+ if (!window.gon?.features?.junitPipelineView) {
+ return;
+ }
+
+ const el = document.querySelector('#js-pipeline-tests-detail');
+ const { fullReportEndpoint, summaryEndpoint, countEndpoint } = el?.dataset || {};
+
+ const testReportsStore = createTestReportsStore({
+ fullReportEndpoint,
+ summaryEndpoint: summaryEndpoint || countEndpoint,
+ useBuildSummaryReport: window.gon?.features?.buildReportSummary,
+ });
+
+ if (!window.gon?.features?.buildReportSummary) {
+ createPipelinesTabs(testReportsStore);
+ }
+
// eslint-disable-next-line no-new
new Vue({
- el: '#js-pipeline-tests-detail',
+ el,
components: {
TestReports,
},
+ store: testReportsStore,
render(createElement) {
return createElement('test-reports');
},
});
-
- axios
- .get(detailsEndpoint)
- .then(({ data }) => {
- if (!data.total_count) {
- return;
- }
-
- document.querySelector('.js-test-report-badge-counter').innerHTML = data.total_count;
- })
- .catch(() => {});
};
const createDagApp = () => {
@@ -151,7 +153,8 @@ const createDagApp = () => {
}
const el = document.querySelector('#js-pipeline-dag-vue');
- const graphUrl = el?.dataset?.pipelineDataPath;
+ const { pipelineDataPath, emptySvgPath, dagDocPath } = el?.dataset;
+
// eslint-disable-next-line no-new
new Vue({
el,
@@ -161,7 +164,9 @@ const createDagApp = () => {
render(createElement) {
return createElement('dag', {
props: {
- graphUrl,
+ graphUrl: pipelineDataPath,
+ emptySvgPath,
+ dagDocPath,
},
});
},
@@ -175,7 +180,6 @@ export default () => {
createPipelinesDetailApp(mediator);
createPipelineHeaderApp(mediator);
- createPipelinesTabs(dataset);
- createTestDetails(dataset.testReportsCountEndpoint);
+ createTestDetails();
createDagApp();
};
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/actions.js b/app/assets/javascripts/pipelines/stores/test_reports/actions.js
index 71d875c1a83..ccacb9f7e97 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/actions.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/actions.js
@@ -3,17 +3,42 @@ import * as types from './mutation_types';
import createFlash from '~/flash';
import { s__ } from '~/locale';
-export const setEndpoint = ({ commit }, data) => commit(types.SET_ENDPOINT, data);
+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');
+ }
-export const fetchReports = ({ state, commit, dispatch }) => {
+ 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');
+ }
+ });
+};
+
+export const fetchFullReport = ({ state, commit, dispatch }) => {
dispatch('toggleLoading');
return axios
- .get(state.endpoint)
- .then(response => {
- const { data } = response;
- commit(types.SET_REPORTS, data);
- })
+ .get(state.fullReportEndpoint)
+ .then(({ data }) => commit(types.SET_REPORTS, data))
.catch(() => {
createFlash(s__('TestReports|There was an error fetching the test reports.'));
})
@@ -22,8 +47,10 @@ export const fetchReports = ({ state, commit, dispatch }) => {
});
};
-export const setSelectedSuite = ({ commit }, data) => commit(types.SET_SELECTED_SUITE, data);
-export const removeSelectedSuite = ({ commit }) => commit(types.SET_SELECTED_SUITE, {});
+export const setSelectedSuiteIndex = ({ commit }, data) =>
+ commit(types.SET_SELECTED_SUITE_INDEX, 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
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/getters.js b/app/assets/javascripts/pipelines/stores/test_reports/getters.js
index 788c1d32987..877762b77c9 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/getters.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/getters.js
@@ -9,14 +9,12 @@ export const getTestSuites = state => {
}));
};
-export const getSuiteTests = state => {
- const { selectedSuite } = state;
-
- if (selectedSuite.test_cases) {
- return selectedSuite.test_cases.sort(sortTestCases).map(addIconStatus);
- }
+export const getSelectedSuite = state =>
+ state.testReports?.test_suites?.[state.selectedSuiteIndex] || {};
- return [];
+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
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/index.js b/app/assets/javascripts/pipelines/stores/test_reports/index.js
index 318dff5bcb2..88f61b09025 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/index.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/index.js
@@ -7,9 +7,10 @@ import mutations from './mutations';
Vue.use(Vuex);
-export default new Vuex.Store({
- actions,
- getters,
- mutations,
- state,
-});
+export default initialState =>
+ new Vuex.Store({
+ actions,
+ getters,
+ mutations,
+ state: state(initialState),
+ });
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 832e45cf7a1..76405557b51 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_ENDPOINT = 'SET_ENDPOINT';
export const SET_REPORTS = 'SET_REPORTS';
-export const SET_SELECTED_SUITE = 'SET_SELECTED_SUITE';
+export const SET_SELECTED_SUITE_INDEX = 'SET_SELECTED_SUITE_INDEX';
+export const SET_SUMMARY = 'SET_SUMMARY';
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 349e6ec0469..2531ab1e87c 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
@@ -1,16 +1,16 @@
import * as types from './mutation_types';
export default {
- [types.SET_ENDPOINT](state, endpoint) {
- Object.assign(state, { endpoint });
+ [types.SET_REPORTS](state, testReports) {
+ Object.assign(state, { testReports, hasFullReport: true });
},
- [types.SET_REPORTS](state, testReports) {
- Object.assign(state, { testReports });
+ [types.SET_SELECTED_SUITE_INDEX](state, selectedSuiteIndex) {
+ Object.assign(state, { selectedSuiteIndex });
},
- [types.SET_SELECTED_SUITE](state, selectedSuite) {
- Object.assign(state, { selectedSuite });
+ [types.SET_SUMMARY](state, summary) {
+ Object.assign(state, { testReports: { ...state.testReports, ...summary } });
},
[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 80a0c2a46a0..bcf5c147916 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/state.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/state.js
@@ -1,6 +1,13 @@
-export default () => ({
- endpoint: '',
+export default ({
+ fullReportEndpoint = '',
+ summaryEndpoint = '',
+ useBuildSummaryReport = false,
+}) => ({
+ summaryEndpoint,
+ fullReportEndpoint,
testReports: {},
- selectedSuite: {},
+ selectedSuiteIndex: null,
+ hasFullReport: false,
isLoading: false,
+ useBuildSummaryReport,
});