Skip to content
Snippets Groups Projects
Commit cd98ee1e authored by Matthias Käppler's avatar Matthias Käppler 🚴🏿
Browse files

Merge branch 'update-rest-mini-pipeline-graph' into 'master'

parents ddd9b83e 412f7af0
No related branches found
No related tags found
No related merge requests found
Showing
with 181 additions and 12 deletions
Loading
Loading
@@ -171,11 +171,12 @@ export const generateColumnsFromLayersListBare = ({ stages, stagesLookup }, pipe
 
export const generateColumnsFromLayersListMemoized = memoize(generateColumnsFromLayersListBare);
 
// TODO: handle REST / MR values
// See https://gitlab.com/gitlab-org/gitlab/-/issues/367547
export const keepLatestDownstreamPipelines = (downstreamPipelines = []) => {
// handles GraphQL
return downstreamPipelines.filter((pipeline) => {
if (pipeline.source_job) {
return !pipeline?.source_job?.retried || false;
}
return !pipeline?.sourceJob?.retried || false;
});
};
Loading
Loading
@@ -2,6 +2,7 @@
import { GlTableLite, GlTooltipDirective } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import Tracking from '~/tracking';
import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils';
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
import eventHub from '../../event_hub';
import { TRACKING_CATEGORIES } from '../../constants';
Loading
Loading
@@ -115,6 +116,10 @@ export default {
eventHub.$off('openConfirmationModal', this.setModalData);
},
methods: {
getDownstreamPipelines(pipeline) {
const downstream = pipeline.triggered;
return keepLatestDownstreamPipelines(downstream);
},
setModalData(data) {
this.pipelineId = data.pipeline.id;
this.pipeline = data.pipeline;
Loading
Loading
@@ -171,7 +176,7 @@ export default {
 
<template #cell(stages)="{ item }">
<pipeline-mini-graph
:downstream-pipelines="item.triggered"
:downstream-pipelines="getDownstreamPipelines(item)"
:pipeline-path="item.path"
:stages="item.details.stages"
:update-dropdown="updateGraphDropdown"
Loading
Loading
Loading
Loading
@@ -11,6 +11,7 @@ import {
import SafeHtml from '~/vue_shared/directives/safe_html';
import { s__, n__ } from '~/locale';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils';
import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue';
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
Loading
Loading
@@ -86,6 +87,10 @@ export default {
},
},
computed: {
downstreamPipelines() {
const downstream = this.pipeline.triggered;
return keepLatestDownstreamPipelines(downstream);
},
hasPipeline() {
return this.pipeline && Object.keys(this.pipeline).length > 0;
},
Loading
Loading
@@ -274,7 +279,7 @@ export default {
<span class="gl-align-items-center gl-display-inline-flex">
<pipeline-mini-graph
v-if="pipeline.details.stages"
:downstream-pipelines="pipeline.triggered"
:downstream-pipelines="downstreamPipelines"
:is-merge-train="isMergeTrain"
:pipeline-path="pipeline.path"
:stages="pipeline.details.stages"
Loading
Loading
Loading
Loading
@@ -15,6 +15,9 @@ class TriggeredPipelineEntity < Grape::Entity
expose :name do |pipeline|
pipeline.source_job&.name
end
expose :retried do |pipeline|
pipeline.source_job&.retried
end
end
 
expose :path do |pipeline|
Loading
Loading
const downstream = {
export const mockDownstreamPipelinesGraphql = ({ includeSourceJobRetried = true } = {}) => ({
nodes: [
{
id: 'gid://gitlab/Ci::Pipeline/612',
Loading
Loading
@@ -17,7 +17,7 @@ const downstream = {
},
sourceJob: {
id: 'gid://gitlab/Ci::Bridge/532',
retried: false,
retried: includeSourceJobRetried ? false : null,
},
__typename: 'Pipeline',
},
Loading
Loading
@@ -38,7 +38,7 @@ const downstream = {
},
sourceJob: {
id: 'gid://gitlab/Ci::Bridge/531',
retried: true,
retried: includeSourceJobRetried ? true : null,
},
__typename: 'Pipeline',
},
Loading
Loading
@@ -59,13 +59,13 @@ const downstream = {
},
sourceJob: {
id: 'gid://gitlab/Ci::Bridge/530',
retried: true,
retried: includeSourceJobRetried ? true : null,
},
__typename: 'Pipeline',
},
],
__typename: 'PipelineConnection',
};
});
 
const upstream = {
id: 'gid://gitlab/Ci::Pipeline/610',
Loading
Loading
@@ -161,7 +161,7 @@ export const mockDownstreamQueryResponse = {
pipeline: {
path: '/root/ci-project/-/pipelines/790',
id: 'pipeline-1',
downstream,
downstream: mockDownstreamPipelinesGraphql(),
upstream: null,
},
__typename: 'Project',
Loading
Loading
@@ -176,7 +176,7 @@ export const mockUpstreamDownstreamQueryResponse = {
pipeline: {
id: 'pipeline-1',
path: '/root/ci-project/-/pipelines/790',
downstream,
downstream: mockDownstreamPipelinesGraphql(),
upstream,
},
__typename: 'Project',
Loading
Loading
Loading
Loading
@@ -23,8 +23,19 @@
let!(:build_test) { create(:ci_build, pipeline: pipeline, stage: 'test') }
let!(:build_deploy_failed) { create(:ci_build, status: :failed, pipeline: pipeline, stage: 'deploy') }
 
let(:bridge) { create(:ci_bridge, pipeline: pipeline) }
let(:retried_bridge) { create(:ci_bridge, :retried, pipeline: pipeline) }
let(:downstream_pipeline) { create(:ci_pipeline, :with_job) }
let(:retried_downstream_pipeline) { create(:ci_pipeline, :with_job) }
let!(:ci_sources_pipeline) { create(:ci_sources_pipeline, pipeline: downstream_pipeline, source_job: bridge) }
let!(:retried_ci_sources_pipeline) do
create(:ci_sources_pipeline, pipeline: retried_downstream_pipeline, source_job: retried_bridge)
end
before do
sign_in(user)
project.add_developer(user)
end
 
it 'pipelines/pipelines.json' do
Loading
Loading
Loading
Loading
@@ -121,6 +121,14 @@ describe('Pipelines Table', () => {
expect(findPipelineMiniGraph().props('stages').length).toBe(stagesLength);
});
 
it('should render the latest downstream pipelines only', () => {
// component receives two downstream pipelines. one of them is already outdated
// because we retried the trigger job, so the mini pipeline graph will only
// render the newly created downstream pipeline instead
expect(pipeline.triggered).toHaveLength(2);
expect(findPipelineMiniGraph().props('downstreamPipelines')).toHaveLength(1);
});
describe('when pipeline does not have stages', () => {
beforeEach(() => {
pipeline = createMockPipeline();
Loading
Loading
Loading
Loading
@@ -3,6 +3,7 @@ import {
makeLinksFromNodes,
filterByAncestors,
generateColumnsFromLayersListBare,
keepLatestDownstreamPipelines,
listByLayers,
parseData,
removeOrphanNodes,
Loading
Loading
@@ -10,6 +11,8 @@ import {
} from '~/pipelines/components/parsing_utils';
import { createNodeDict } from '~/pipelines/utils';
 
import { mockDownstreamPipelinesRest } from '../vue_merge_request_widget/mock_data';
import { mockDownstreamPipelinesGraphql } from '../commit/mock_data';
import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data';
import { generateResponse, mockPipelineResponse } from './graph/mock_data';
 
Loading
Loading
@@ -159,3 +162,37 @@ describe('DAG visualization parsing utilities', () => {
});
});
});
describe('linked pipeline utilities', () => {
describe('keepLatestDownstreamPipelines', () => {
it('filters data from GraphQL', () => {
const downstream = mockDownstreamPipelinesGraphql().nodes;
const latestDownstream = keepLatestDownstreamPipelines(downstream);
expect(downstream).toHaveLength(3);
expect(latestDownstream).toHaveLength(1);
});
it('filters data from REST', () => {
const downstream = mockDownstreamPipelinesRest();
const latestDownstream = keepLatestDownstreamPipelines(downstream);
expect(downstream).toHaveLength(2);
expect(latestDownstream).toHaveLength(1);
});
it('returns downstream pipelines if sourceJob.retried is null', () => {
const downstream = mockDownstreamPipelinesGraphql({ includeSourceJobRetried: false }).nodes;
const latestDownstream = keepLatestDownstreamPipelines(downstream);
expect(latestDownstream).toHaveLength(downstream.length);
});
it('returns downstream pipelines if source_job.retried is null', () => {
const downstream = mockDownstreamPipelinesRest({ includeSourceJobRetried: false });
const latestDownstream = keepLatestDownstreamPipelines(downstream);
expect(latestDownstream).toHaveLength(downstream.length);
});
});
});
Loading
Loading
@@ -111,6 +111,14 @@ describe('MRWidgetPipeline', () => {
expect(findPipelineMiniGraph().props('stages')).toHaveLength(stagesCount);
});
 
it('should render the latest downstream pipelines only', () => {
// component receives two downstream pipelines. one of them is already outdated
// because we retried the trigger job, so the mini pipeline graph will only
// render the newly created downstream pipeline instead
expect(mockData.pipeline.triggered).toHaveLength(2);
expect(findPipelineMiniGraph().props('downstreamPipelines')).toHaveLength(1);
});
describe('should render pipeline coverage information', () => {
it('should render coverage percentage', () => {
expect(findPipelineCoverage().text()).toMatch(
Loading
Loading
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
 
export const mockDownstreamPipelinesRest = ({ includeSourceJobRetried = true } = {}) => [
{
id: 632,
user: {
id: 1,
username: 'root',
name: 'Administrator',
state: 'active',
avatar_url:
'https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
web_url: 'https://gdk.test:3000/root',
show_status: false,
path: '/root',
},
active: false,
coverage: null,
source: 'parent_pipeline',
source_job: {
name: 'bridge_job',
retried: includeSourceJobRetried ? false : null,
},
path: '/kitchen-sink/bakery/-/pipelines/632',
details: {
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/kitchen-sink/bakery/-/pipelines/632',
illustration: null,
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
},
},
project: {
id: 21,
name: 'bakery',
full_path: '/kitchen-sink/bakery',
full_name: 'kitchen-sink / bakery',
refs_url: '/kitchen-sink/bakery/refs',
},
},
{
id: 633,
user: {
id: 1,
username: 'root',
name: 'Administrator',
state: 'active',
avatar_url:
'https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
web_url: 'https://gdk.test:3000/root',
show_status: false,
path: '/root',
},
active: false,
coverage: null,
source: 'parent_pipeline',
source_job: {
name: 'bridge_job',
retried: includeSourceJobRetried ? true : null,
},
path: '/kitchen-sink/bakery/-/pipelines/633',
details: {
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/kitchen-sink/bakery/-/pipelines/633',
illustration: null,
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
},
},
project: {
id: 21,
name: 'bakery',
full_path: '/kitchen-sink/bakery',
full_name: 'kitchen-sink / bakery',
refs_url: '/kitchen-sink/bakery/refs',
},
},
];
export const artifacts = [
{
text: 'result.txt',
Loading
Loading
@@ -207,6 +296,7 @@ export default {
retry_path: '/root/acets-app/pipelines/172/retry',
created_at: '2017-04-07T12:27:19.520Z',
updated_at: '2017-04-07T15:28:44.800Z',
triggered: mockDownstreamPipelinesRest(),
},
pipelineCoverageDelta: '15.25',
buildsWithCoverage: [
Loading
Loading
Loading
Loading
@@ -182,6 +182,7 @@
 
expect(source_jobs[cross_project_pipeline.id][:name]).to eq('cross-project')
expect(source_jobs[child_pipeline.id][:name]).to eq('child')
expect(source_jobs[child_pipeline.id][:retried]).to eq false
end
end
end
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment