Commit 3f035d79 authored by Filipa Lacerda's avatar Filipa Lacerda Committed by Phil Hughes

Adds EE store for upstream/downstream pipelines

parent 9f05c6ac
......@@ -13,8 +13,4 @@ export default class PipelineService {
postAction(endpoint) {
return axios.post(`${endpoint}.json`);
}
static getSecurityReport(endpoint) {
return axios.get(endpoint);
}
}
export default {
triggeredPipelines: 'triggeredPipelines',
triggeredByPipelines: 'triggeredByPipelines',
triggeredBy: 'triggeredBy',
triggered: 'triggered',
};
import axios from '~/lib/utils/axios_utils';
import CePipelineService from '~/pipelines/services/pipeline_service';
export default class PipelineStore extends CePipelineService {
static getUpstreamDownstream(endpoint) {
return axios.get(`${endpoint}.json`);
}
}
import CePipelineStore from '~/pipelines/stores/pipeline_store';
import pipelinesKeys from '../constants';
/**
* Extends CE store with the logic to handle the upstream/downstream pipelines
*/
export default class PipelineStore extends CePipelineStore {
constructor() {
super();
// Stores the dowsntream collapsed pipelines
// with basic info sent in the main request
this.state.triggeredPipelines = [];
// Stores the upstream collapsed pipelines
// with basic info sent in the main request
this.state.triggeredByPipelines = [];
// Visible downstream pipeline
this.state.triggered = {};
// Visible upstream pipeline
this.state.triggeredBy = {};
}
/**
* For the triggered pipelines, parses them to add `isLoading` and `isCollapsed` keys
*
* For the triggered_by pipeline, parsed the object to add `isLoading` and `isCollapsed` keys
* and saves it as an array
*
* @param {Object} pipeline
*/
storePipeline(pipeline = {}) {
super.storePipeline(pipeline);
if (pipeline.triggered && pipeline.triggered.length) {
this.state.triggeredPipelines = pipeline.triggered.map(triggered =>
PipelineStore.parsePipeline(triggered),
);
}
if (pipeline.triggered_by) {
this.state.triggeredByPipelines = [PipelineStore.parsePipeline(pipeline.triggered_by)];
}
}
//
// Downstream pipeline's methods
//
/**
* Called when the user clicks on a pipeline that was triggered by the main one.
*
* Resets isCollapsed and isLoading props for all triggered (downstream) pipelines
* Sets isLoading to true for the requested one.
*
* @param {Object} pipeline
*/
requestTriggeredPipeline(pipeline) {
this.updateStoreOnRequest(pipelinesKeys.triggeredPipelines, pipeline);
}
/**
* Called when we receive success callback for the downstream pipeline requested.
*
* Updates loading state for the request pipeline
* Updates the visible pipeline with the response
*
* @param {Object} pipeline
* @param {Object} response
*/
receiveTriggeredPipelineSuccess(pipeline, response) {
this.updatePipeline(
pipelinesKeys.triggeredPipelines,
pipeline,
{ isLoading: false },
pipelinesKeys.triggered,
response,
);
}
/**
* Called when we receive an error callback for the downstream pipeline requested
* Resets the loading state + collpased state
* Resets triggered pipeline
*
* @param {Object} pipeline
*/
receiveTriggeredPipelineError(pipeline) {
this.updatePipeline(
pipelinesKeys.triggeredPipelines,
pipeline,
{ isLoading: false, isCollapsed: true },
pipelinesKeys.triggered,
{},
);
}
//
// Upstream pipeline's methods
//
/**
* Called when the user clicks on the pipeline that triggered the main one.
*
* Handle the request for the upstream pipeline
* Updates the given pipeline with isLoading: true and iscollapsed: false
*
* @param {Object} pipeline
*/
requestTriggeredByPipeline(pipeline) {
this.updateStoreOnRequest(pipelinesKeys.triggeredByPipelines, pipeline);
}
/**
* Success callback for the upstream pipeline received
*
* @param {Object} pipeline
* @param {Object} response
*/
receiveTriggeredByPipelineSuccess(pipeline, response) {
this.updatePipeline(
pipelinesKeys.triggeredByPipelines,
pipeline,
{ isLoading: false },
pipelinesKeys.triggeredBy,
response,
);
}
/**
* Error callback for the upstream callback
* @param {Object} pipeline
*/
receiveTriggeredByPipelineError(pipeline) {
this.updatePipeline(
pipelinesKeys.triggeredByPipelines,
pipeline,
{ isLoading: false, isCollapsed: true },
pipelinesKeys.triggeredBy,
{},
);
}
//
// Common utils between upstream & dowsntream pipelines
//
/**
* Adds isLoading and isCollpased keys to the given pipeline
*
* Used to know when to render the spinning icon
* and the blue background when the pipeline is expanded.
*
* @param {Object} pipeline
* @returns {Object}
*/
static parsePipeline(pipeline) {
return Object.assign({}, pipeline, {
isCollapsed: true,
isLoading: false,
});
}
/**
* Returns the index of the upstream/downstream that matches the given ID
*
* @param {Object} pipeline
* @returns {Number}
*/
getPipelineIndex(storeKey, pipelineId) {
return this.state[storeKey].findIndex(triggered => triggered.id === pipelineId);
}
/**
* Updates the pipelines to reflect which one was requested.
* It sets isLoading to true and isCollapsed to false
*
* @param {String} storeKey which property to update: `triggeredPipelines|triggeredByPipelines`
* @param {Object} pipeline the requested pipeline
*/
updateStoreOnRequest(storeKey, pipeline) {
this.state[storeKey] = this.state[storeKey].map(triggered => {
if (triggered.id === pipeline.id) {
return Object.assign({}, triggered, { isLoading: true, isCollapsed: false });
}
// reset the others, in case another was one opened
return PipelineStore.parsePipeline(triggered);
});
}
/**
* Updates a single pipeline with the new props and the visible pipeline
* Used for success and error callbacks for both upstream and downstream requests.
*
* @param {String} storeKey Which array needs to be updated: `triggeredPipelines|triggeredByPipelines`
* @param {Object} pipeline Which pipeline should be updated
* @param {Object} props The new properties to be updated for the given pipeline
* @param {String} visiblePipelineKey Which visible pipeline needs to be updated: `triggered|triggeredBy`
* @param {Object} visiblePipeline The new visible pipeline value
*/
updatePipeline(storeKey, pipeline, props, visiblePipelineKey, visiblePipeline = {}) {
this.state[storeKey].splice(
this.getPipelineIndex(storeKey, pipeline.id),
1,
Object.assign({}, pipeline, props),
);
this.state[visiblePipelineKey] = visiblePipeline;
}
/**
* When the user clicks on a non collapsed pipeline we need to close it
*
* @param {String} storeKey Which array needs to be updated: `triggeredPipelines|triggeredByPipelines`
* @param {Object} pipeline Which pipeline should be updated
* @param {String} visiblePipelineKey Which visible pipeline needs to be updated: `triggered|triggeredBy`
*/
closePipeline(storeKey, pipeline, visiblePipelineKey) {
this.updatePipeline(
storeKey,
pipeline,
{
isLoading: false,
isCollapsed: true,
},
visiblePipelineKey,
{},
);
}
}
---
title: Adds EE store to handle upstream & downstream pipelines
merge_request:
author:
type: added
{
"id": 37232567,
"user": {
"id": 113870,
"name": "Phil Hughes",
"username": "iamphill",
"state": "active",
"avatar_url": "https://secure.gravatar.com/avatar/533a51534470a11062df393543eab649?s=80\u0026d=identicon",
"web_url": "https://gitlab.com/iamphill",
"status_tooltip_html": null,
"path": "/iamphill"
},
"active": false,
"coverage": null,
"source": "push",
"created_at": "2018-11-20T10:22:52.617Z",
"updated_at": "2018-11-20T10:24:09.511Z",
"path": "/gitlab-org/gl-vue-cli/pipelines/37232567",
"flags": {
"latest": true,
"stuck": false,
"auto_devops": false,
"yaml_errors": false,
"retryable": false,
"cancelable": false,
"failure_reason": false
},
"details": {
"status": {
"icon": "status_success",
"text": "passed",
"label": "passed",
"group": "success",
"tooltip": "passed",
"has_details": true,
"details_path": "/gitlab-org/gl-vue-cli/pipelines/37232567",
"illustration": null,
"favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
},
"duration": 65,
"finished_at": "2018-11-20T10:24:09.483Z",
"stages": [
{
"name": "test",
"title": "test: passed",
"groups": [
{
"name": "eslint",
"size": 1,
"status": {
"icon": "status_success",
"text": "passed",
"label": "passed",
"group": "success",
"tooltip": "passed",
"has_details": true,
"details_path": "/gitlab-org/gl-vue-cli/-/jobs/122845352",
"illustration": {
"image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
"size": "svg-430",
"title": "This job does not have a trace."
},
"favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
"action": {
"icon": "retry",
"title": "Retry",
"path": "/gitlab-org/gl-vue-cli/-/jobs/122845352/retry",
"method": "post",
"button_title": "Retry this job"
}
},
"jobs": [
{
"id": 122845352,
"name": "eslint",
"started": "2018-11-20T10:22:53.369Z",
"archived": false,
"build_path": "/gitlab-org/gl-vue-cli/-/jobs/122845352",
"retry_path": "/gitlab-org/gl-vue-cli/-/jobs/122845352/retry",
"playable": false,
"scheduled": false,
"created_at": "2018-11-20T10:22:52.630Z",
"updated_at": "2018-11-20T10:23:58.948Z",
"status": {
"icon": "status_success",
"text": "passed",
"label": "passed",
"group": "success",
"tooltip": "passed",
"has_details": true,
"details_path": "/gitlab-org/gl-vue-cli/-/jobs/122845352",
"illustration": {
"image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
"size": "svg-430",
"title": "This job does not have a trace."
},
"favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
"action": {
"icon": "retry",
"title": "Retry",
"path": "/gitlab-org/gl-vue-cli/-/jobs/122845352/retry",
"method": "post",
"button_title": "Retry this job"
}
}
}
]
}
],
"status": {
"icon": "status_success",
"text": "passed",
"label": "passed",
"group": "success",
"tooltip": "passed",
"has_details": true,
"details_path": "/gitlab-org/gl-vue-cli/pipelines/37232567#test",
"illustration": null,
"favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
},
"path": "/gitlab-org/gl-vue-cli/pipelines/37232567#test",
"dropdown_path": "/gitlab-org/gl-vue-cli/pipelines/37232567/stage.json?stage=test"
}
],
"artifacts": [],
"manual_actions": [],
"scheduled_actions": []
},
"ref": {
"name": "master",
"path": "/gitlab-org/gl-vue-cli/commits/master",
"tag": false,
"branch": true
},
"commit": {
"id": "8f179601d481950bcb67032caeb33d1c24dde6bd",
"short_id": "8f179601",
"title": "Merge branch 'gl-cli-startt' into 'master'",
"created_at": "2018-11-20T10:22:51.000Z",
"parent_ids": [
"781d78fcd3d6c17ccf208f0cf0ab47c3e5397118",
"d227a0bb858c48eeee7393fcade1a33748f35183"
],
"message": "Merge branch 'gl-cli-startt' into 'master'\n\nFirst iteration of the CLI\n\nCloses gitlab-ce#53657\n\nSee merge request gitlab-org/gl-vue-cli!2",
"author_name": "Phil Hughes",
"author_email": "me@iamphill.com",
"authored_date": "2018-11-20T10:22:51.000Z",
"committer_name": "Phil Hughes",
"committer_email": "me@iamphill.com",
"committed_date": "2018-11-20T10:22:51.000Z",
"author": {
"id": 113870,
"name": "Phil Hughes",
"username": "iamphill",
"state": "active",
"avatar_url": "https://secure.gravatar.com/avatar/533a51534470a11062df393543eab649?s=80\u0026d=identicon",
"web_url": "https://gitlab.com/iamphill",
"status_tooltip_html": null,
"path": "/iamphill"
},
"author_gravatar_url": "https://secure.gravatar.com/avatar/533a51534470a11062df393543eab649?s=80\u0026d=identicon",
"commit_url": "https://gitlab.com/gitlab-org/gl-vue-cli/commit/8f179601d481950bcb67032caeb33d1c24dde6bd",
"commit_path": "/gitlab-org/gl-vue-cli/commit/8f179601d481950bcb67032caeb33d1c24dde6bd"
},
"triggered_by": null,
"triggered": []
}
import PipelineStore from 'ee/pipelines/stores/pipeline_store';
import pipelineWithTriggered from './pipeline_with_triggered.json';
import pipelineWithTriggeredBy from './pipeline_with_triggered_by.json';
import pipelineWithBoth from './pipeline_with_triggered_triggered_by.json';
import pipeline from './pipeline.json';
describe('EE Pipeline store', () => {
let store;
beforeEach(() => {
store = new PipelineStore();
});
describe('storePipeline', () => {
describe('triggeredPipelines ', () => {
describe('with triggered pipelines', () => {
it('saves parsed pipelines', () => {
store.storePipeline(pipelineWithTriggered);
expect(store.triggeredPipelines.length).toEqual(pipelineWithTriggered.triggered.length);
expect(store.triggeredPipelines[0]).toEqual(
Object.assign({}, pipelineWithTriggered.triggered[0], {
isLoading: false,
isCollpased: true,
}),
);
});
});
describe('without triggered pipelines', () => {
it('triggeredPipelines should be an empty array', () => {
store.storePipeline({ triggered: [] });
expect(store.triggeredPipelines).toEqual([]);
});
});
});
describe('triggeredByPipelines', () => {
describe('with triggered_by pipeline', () => {
store.storePipeline(pipelineWithTriggeredBy);
expect(store.pipelineWithTriggeredBy.length).toEqual(1);
expect(store.triggeredByPipelines[0]).toEqual(
Object.assign({}, pipelineWithTriggeredBy.triggered_by, {
isLoading: false,
isCollpased: true,
}),
);
});
describe('without triggered_by pipeline', () => {
it('triggeredByPipelines should be an empty array', () => {
store.storePipeline({ triggered_by: null });
expect(store.triggeredByPipelines).toEqual([]);
});
});
});
});
describe('downstream', () => {
beforeAll(() => {
store.storePipeline(pipelineWithBoth);
});
describe('requestTriggeredPipeline', () => {
beforeEach(() => {
store.requestTriggeredPipeline(store.triggeredPipelines[0]);
});
it('sets isLoading to true for the requested pipeline', () => {
expect(store.triggeredPipelines[0].isLoading).toEqual(true);
});
it('sets isCollapsed to false for the requested pipeline', () => {
expect(store.triggeredPipelines[0].isCollapsed).toEqual(false);
});
it('sets isLoading to false for the other pipelines', () => {
expect(store.triggeredPipelines[1].isLoading).toEqual(false);
});
it('sets isCollapsed to true for the other pipelines', () => {
expect(store.triggeredPipelines[1].isCollapsed).toEqual(true);
});
});
describe('receiveTriggeredPipelineSuccess', () => {
it('updates the given pipeline and sets it as the visible one', () => {
const receivedPipeline = store.triggeredPipelines[0];
store.receiveTriggeredPipelineSuccess(receivedPipeline);
expect(store.triggeredPipelines[0].isLoading).toEqual(false);
expect(store.triggered).toEqual(receivedPipeline);
});
});
describe('receiveTriggeredPipelineError', () => {
it('resets the given pipeline and resets it as the visible one', () => {
const receivedPipeline = store.triggeredPipelines[0];
store.receiveTriggeredPipelineError(receivedPipeline);
expect(store.triggeredPipelines[0].isLoading).toEqual(false);
expect(store.triggeredPipelines[0].isCollapsed).toEqual(true);
expect(store.triggered).toEqual({});
});
});
});
describe('upstream', () => {
describe('requestTriggeredByPipeline', () => {
beforeEach(() => {
store.requestTriggeredByPipeline(store.triggeredByPipelines[0]);
});
it('sets isLoading to true for the requested pipeline', () => {
expect(store.triggeredByPipelines[0].isLoading).toEqual(true);
});
it('sets isCollapsed to false for the requested pipeline', () => {
expect(store.triggeredByPipelines[0].isCollapsed).toEqual(false);
});
});
describe('receiveTriggeredByPipelineSuccess', () => {
it('updates the given pipeline and sets it as the visible one', () => {
const receivedPipeline = store.triggeredByPipelines[0];
store.receiveTriggeredByPipelineSuccess(receivedPipeline);
expect(store.triggeredByPipelines[0].isLoading).toEqual(false);
expect(store.triggeredBy).toEqual(receivedPipeline);
});
});
describe('receiveTriggeredByPipelineError', () => {
it('resets the given pipeline and resets it as the visible one', () => {
const receivedPipeline = store.triggeredByPipelines[0];
store.receiveTriggeredByPipelineError(receivedPipeline);
expect(store.triggeredByPipelines[0].isLoading).toEqual(false);
expect(store.triggeredByPipelines[0].isCollapsed).toEqual(true);
expect(store.triggeredBy).toEqual({});
});
});
});
describe('utils', () => {
describe('parsePipeline', () => {
let parsed;
beforeAll(() => {
parsed = PipelineStore.parsePipeline(pipeline);
});
it('adds isLoading key set to false', () => {
expect(parsed.isLoading).toEqual(false);
});
it('adds isCollapsed key set to true', () => {
expect(parsed.isCollapsed).toEqual(true);
});
});
describe('getPipelineIndex', () => {
beforeAll(() => {
store.storePipeline(pipelineWithBoth);
});
it('returns the pipeline index for the provided pipeline and storeKey', () => {
store.getPipelineIndex('triggeredPipelines', store.triggeredPipelines[1]).toEqual(1);
});
});
describe('updateStoreOnRequest', () => {
beforeAll(() => {
store.storePipeline(pipelineWithBoth);
});
it('sets clicked pipeline isLoading to true', () => {
store.updateStoreOnRequest('triggeredPipelines', store.triggeredPipelines[1]);
expect(store.triggeredPipelines[1].isLoading).isLoading(true);
});
it('sets clicked pipeline isCollapsed to false', () => {
store.updateStoreOnRequest('triggeredPipelines', store.triggeredPipelines[1]);
expect(store.triggeredPipelines[1].isCollapsed).isLoading(false);
});
});
describe('updatePipeline', () => {
beforeAll(() => {
store.storePipeline(pipelineWithBoth);
store.updatePipeline(
'triggeredPipelines',
store.triggeredPipelines[1],
{ isLoading: true },
'triggered',
store.triggeredPipelines[1],
);
});
it('updates the given pipeline in the correct array', () => {
expect(store.triggeredPipelines[1].isLoading).toEqual(true);
expect(store.triggered).toEqual(store.triggeredPipelines[1]);
});
it('updates the visible pipeline to the given value', () => {});
});
describe('closePipeline', () => {
beforeAll(() => {
store.storePipeline(pipelineWithBoth);
});
it('closes the given pipeline', () => {
const clickedPipeline = store.triggeredPipelines[1];
// open it first
clickedPipeline.isCollapsed = false;
store.triggered = clickedPipeline;
store.closePipeline('triggeredPipelines', clickedPipeline, 'triggered');
expect(store.triggeredPipelines[1].isCollapsed).toEqual(false);
expect(store.triggered).toEqual({});
});
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment