summaryrefslogtreecommitdiff
path: root/spec/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'spec/javascripts')
-rw-r--r--spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js63
-rw-r--r--spec/javascripts/blob/viewer/index_spec.js4
-rw-r--r--spec/javascripts/boards/board_card_spec.js14
-rw-r--r--spec/javascripts/boards/boards_store_spec.js132
-rw-r--r--spec/javascripts/boards/issue_card_spec.js18
-rw-r--r--spec/javascripts/boards/mock_data.js4
-rw-r--r--spec/javascripts/commit/commit_pipeline_status_component_spec.js106
-rw-r--r--spec/javascripts/create_cluster/.eslintrc.yml3
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js11
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js10
-rw-r--r--spec/javascripts/flash_spec.js12
-rw-r--r--spec/javascripts/frequent_items/components/app_spec.js7
-rw-r--r--spec/javascripts/header_spec.js8
-rw-r--r--spec/javascripts/helpers/tracking_helper.js25
-rw-r--r--spec/javascripts/helpers/vue_resource_helper.js11
-rw-r--r--spec/javascripts/ide/components/branches/search_list_spec.js80
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/list_item_spec.js34
-rw-r--r--spec/javascripts/ide/components/error_message_spec.js106
-rw-r--r--spec/javascripts/ide/components/file_row_extra_spec.js21
-rw-r--r--spec/javascripts/ide/components/file_templates/dropdown_spec.js201
-rw-r--r--spec/javascripts/ide/components/ide_tree_list_spec.js14
-rw-r--r--spec/javascripts/ide/components/jobs/list_spec.js67
-rw-r--r--spec/javascripts/ide/components/merge_requests/list_spec.js159
-rw-r--r--spec/javascripts/ide/components/pipelines/list_spec.js137
-rw-r--r--spec/javascripts/ide/components/repo_editor_spec.js83
-rw-r--r--spec/javascripts/ide/mock_data.js227
-rw-r--r--spec/javascripts/ide/stores/actions/project_spec.js197
-rw-r--r--spec/javascripts/ide/stores/actions_spec.js351
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js106
-rw-r--r--spec/javascripts/ide/stores/mutations/file_spec.js19
-rw-r--r--spec/javascripts/ide/stores/mutations_spec.js389
-rw-r--r--spec/javascripts/ide/stores/utils_spec.js225
-rw-r--r--spec/javascripts/integrations/integration_settings_form_spec.js1
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js68
-rw-r--r--spec/javascripts/jobs/components/environments_block_spec.js212
-rw-r--r--spec/javascripts/jobs/components/job_log_spec.js57
-rw-r--r--spec/javascripts/labels_issue_sidebar_spec.js88
-rw-r--r--spec/javascripts/lazy_loader_spec.js18
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js10
-rw-r--r--spec/javascripts/line_highlighter_spec.js28
-rw-r--r--spec/javascripts/monitoring/charts/time_series_spec.js22
-rw-r--r--spec/javascripts/monitoring/components/dashboard_spec.js139
-rw-r--r--spec/javascripts/monitoring/store/actions_spec.js2
-rw-r--r--spec/javascripts/monitoring/store/mutations_spec.js7
-rw-r--r--spec/javascripts/monitoring/store/utils_spec.js41
-rw-r--r--spec/javascripts/monitoring/utils_spec.js249
-rw-r--r--spec/javascripts/notes/components/discussion_filter_spec.js23
-rw-r--r--spec/javascripts/notes/stores/mutation_spec.js17
-rw-r--r--spec/javascripts/pager_spec.js20
-rw-r--r--spec/javascripts/performance_bar/components/detailed_metric_spec.js109
-rw-r--r--spec/javascripts/performance_bar/components/performance_bar_app_spec.js29
-rw-r--r--spec/javascripts/performance_bar/components/request_selector_spec.js46
-rw-r--r--spec/javascripts/pipelines/graph/graph_component_spec.js203
-rw-r--r--spec/javascripts/pipelines/graph/linked_pipeline_spec.js116
-rw-r--r--spec/javascripts/pipelines/graph/linked_pipelines_column_spec.js38
-rw-r--r--spec/javascripts/pipelines/graph/linked_pipelines_mock_data.js407
-rw-r--r--spec/javascripts/pipelines/linked_pipelines_mock.json3532
-rw-r--r--spec/javascripts/pipelines/stores/pipeline.json167
-rw-r--r--spec/javascripts/pipelines/stores/pipeline_store.js165
-rw-r--r--spec/javascripts/pipelines/stores/pipeline_with_triggered.json381
-rw-r--r--spec/javascripts/pipelines/stores/pipeline_with_triggered_by.json379
-rw-r--r--spec/javascripts/pipelines/stores/pipeline_with_triggered_triggered_by.json452
-rw-r--r--spec/javascripts/registry/components/app_spec.js129
-rw-r--r--spec/javascripts/registry/components/collapsible_container_spec.js87
-rw-r--r--spec/javascripts/registry/components/table_registry_spec.js189
-rw-r--r--spec/javascripts/registry/mock_data.js134
-rw-r--r--spec/javascripts/registry/stores/actions_spec.js132
-rw-r--r--spec/javascripts/registry/stores/mutations_spec.js85
-rw-r--r--spec/javascripts/releases/components/release_block_spec.js170
-rw-r--r--spec/javascripts/releases/list/components/app_spec.js (renamed from spec/javascripts/releases/components/app_spec.js)6
-rw-r--r--spec/javascripts/releases/list/store/actions_spec.js (renamed from spec/javascripts/releases/store/actions_spec.js)8
-rw-r--r--spec/javascripts/releases/list/store/helpers.js (renamed from spec/javascripts/releases/store/helpers.js)2
-rw-r--r--spec/javascripts/releases/list/store/mutations_spec.js (renamed from spec/javascripts/releases/store/mutations_spec.js)8
-rw-r--r--spec/javascripts/reports/components/grouped_test_reports_app_spec.js10
-rw-r--r--spec/javascripts/sidebar/assignee_title_spec.js14
-rw-r--r--spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js14
-rw-r--r--spec/javascripts/sidebar/confidential_issue_sidebar_spec.js78
-rw-r--r--spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js13
-rw-r--r--spec/javascripts/sidebar/subscriptions_spec.js9
-rw-r--r--spec/javascripts/sidebar/todo_spec.js171
-rw-r--r--spec/javascripts/test_bundle.js15
-rw-r--r--spec/javascripts/test_constants.js8
-rw-r--r--spec/javascripts/todos_spec.js51
-rw-r--r--spec/javascripts/vue_mr_widget/components/deployment_spec.js6
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js7
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js7
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js1
-rw-r--r--spec/javascripts/vue_mr_widget/stores/artifacts_list/actions_spec.js165
-rw-r--r--spec/javascripts/vue_shared/components/clipboard_button_spec.js6
-rw-r--r--spec/javascripts/vue_shared/components/deprecated_modal_2_spec.js (renamed from spec/javascripts/vue_shared/components/gl_modal_spec.js)14
-rw-r--r--spec/javascripts/vue_shared/components/file_row_spec.js13
-rw-r--r--spec/javascripts/vue_shared/components/icon_spec.js8
-rw-r--r--spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js5
-rw-r--r--spec/javascripts/zen_mode_spec.js2
94 files changed, 8260 insertions, 3177 deletions
diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js
index fd73fb4bfcc..d175c8ba853 100644
--- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js
+++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js
@@ -1,8 +1,10 @@
import sqljs from 'sql.js';
+import axios from '~/lib/utils/axios_utils';
import BalsamiqViewer from '~/blob/balsamiq/balsamiq_viewer';
import ClassSpecHelper from '../../helpers/class_spec_helper';
describe('BalsamiqViewer', () => {
+ const mockArrayBuffer = new ArrayBuffer(10);
let balsamiqViewer;
let viewer;
@@ -19,44 +21,65 @@ describe('BalsamiqViewer', () => {
});
describe('loadFile', () => {
- let xhr;
- let loadFile;
+ let bv;
const endpoint = 'endpoint';
+ const requestSuccess = Promise.resolve({
+ data: mockArrayBuffer,
+ status: 200,
+ });
beforeEach(() => {
- xhr = jasmine.createSpyObj('xhr', ['open', 'send']);
+ viewer = {};
+ bv = new BalsamiqViewer(viewer);
+ });
- balsamiqViewer = jasmine.createSpyObj('balsamiqViewer', ['renderFile']);
+ it('should call `axios.get` on `endpoint` param with responseType set to `arraybuffer', () => {
+ spyOn(axios, 'get').and.returnValue(requestSuccess);
+ spyOn(bv, 'renderFile').and.stub();
- spyOn(window, 'XMLHttpRequest').and.returnValue(xhr);
+ bv.loadFile(endpoint);
- loadFile = BalsamiqViewer.prototype.loadFile.call(balsamiqViewer, endpoint);
+ expect(axios.get).toHaveBeenCalledWith(
+ endpoint,
+ jasmine.objectContaining({
+ responseType: 'arraybuffer',
+ }),
+ );
});
- it('should call .open', () => {
- expect(xhr.open).toHaveBeenCalledWith('GET', endpoint, true);
- });
+ it('should call `renderFile` on request success', done => {
+ spyOn(axios, 'get').and.returnValue(requestSuccess);
+ spyOn(bv, 'renderFile').and.callFake(() => {});
- it('should set .responseType', () => {
- expect(xhr.responseType).toBe('arraybuffer');
+ bv.loadFile(endpoint)
+ .then(() => {
+ expect(bv.renderFile).toHaveBeenCalledWith(mockArrayBuffer);
+ })
+ .then(done)
+ .catch(done.fail);
});
- it('should call .send', () => {
- expect(xhr.send).toHaveBeenCalled();
- });
+ it('should not call `renderFile` on request failure', done => {
+ spyOn(axios, 'get').and.returnValue(Promise.reject());
+ spyOn(bv, 'renderFile');
- it('should return a promise', () => {
- expect(loadFile).toEqual(jasmine.any(Promise));
+ bv.loadFile(endpoint)
+ .then(() => {
+ done.fail('Expected loadFile to throw error!');
+ })
+ .catch(() => {
+ expect(bv.renderFile).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('renderFile', () => {
let container;
- let loadEvent;
let previews;
beforeEach(() => {
- loadEvent = { target: { response: {} } };
viewer = jasmine.createSpyObj('viewer', ['appendChild']);
previews = [document.createElement('ul'), document.createElement('ul')];
@@ -73,11 +96,11 @@ describe('BalsamiqViewer', () => {
container = containerElement;
});
- BalsamiqViewer.prototype.renderFile.call(balsamiqViewer, loadEvent);
+ BalsamiqViewer.prototype.renderFile.call(balsamiqViewer, mockArrayBuffer);
});
it('should call .initDatabase', () => {
- expect(balsamiqViewer.initDatabase).toHaveBeenCalledWith(loadEvent.target.response);
+ expect(balsamiqViewer.initDatabase).toHaveBeenCalledWith(mockArrayBuffer);
});
it('should call .getPreviews', () => {
diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js
index 4ac15ca5aa2..06c06613887 100644
--- a/spec/javascripts/blob/viewer/index_spec.js
+++ b/spec/javascripts/blob/viewer/index_spec.js
@@ -101,7 +101,7 @@ describe('Blob viewer', () => {
it('has tooltip when disabled', () => {
expect(copyButton.getAttribute('data-original-title')).toBe(
- 'Switch to the source to copy it to the clipboard',
+ 'Switch to the source to copy the file contents',
);
});
@@ -136,7 +136,7 @@ describe('Blob viewer', () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => {
- expect(copyButton.getAttribute('data-original-title')).toBe('Copy source to clipboard');
+ expect(copyButton.getAttribute('data-original-title')).toBe('Copy file contents');
done();
});
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index 13b708a03d5..9f441ca319e 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -67,6 +67,16 @@ describe('Board card', () => {
expect(vm.issueDetailVisible).toBe(true);
});
+ it("returns false when multiSelect doesn't contain issue", () => {
+ expect(vm.multiSelectVisible).toBe(false);
+ });
+
+ it('returns true when multiSelect contains issue', () => {
+ boardsStore.multiSelect.list = [vm.issue];
+
+ expect(vm.multiSelectVisible).toBe(true);
+ });
+
it('adds user-can-drag class if not disabled', () => {
expect(vm.$el.classList.contains('user-can-drag')).toBe(true);
});
@@ -180,7 +190,7 @@ describe('Board card', () => {
triggerEvent('mousedown');
triggerEvent('mouseup');
- expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', vm.issue);
+ expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', vm.issue, undefined);
expect(boardsStore.detail.list).toEqual(vm.list);
});
@@ -203,7 +213,7 @@ describe('Board card', () => {
triggerEvent('mousedown');
triggerEvent('mouseup');
- expect(eventHub.$emit).toHaveBeenCalledWith('clearDetailIssue');
+ expect(eventHub.$emit).toHaveBeenCalledWith('clearDetailIssue', undefined);
});
});
});
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 11352140ba4..678fe5befa8 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -12,6 +12,7 @@ import '~/boards/services/board_service';
import boardsStore from '~/boards/stores/boards_store';
import eventHub from '~/boards/eventhub';
import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data';
+import waitForPromises from '../../frontend/helpers/wait_for_promises';
describe('Store', () => {
let mock;
@@ -29,6 +30,13 @@ describe('Store', () => {
}),
);
+ spyOn(gl.boardService, 'moveMultipleIssues').and.callFake(
+ () =>
+ new Promise(resolve => {
+ resolve();
+ }),
+ );
+
Cookies.set('issue_board_welcome_hidden', 'false', {
expires: 365 * 10,
path: '',
@@ -376,4 +384,128 @@ describe('Store', () => {
expect(state.currentBoard).toEqual(dummyBoard);
});
});
+
+ describe('toggleMultiSelect', () => {
+ let basicIssueObj;
+
+ beforeAll(() => {
+ basicIssueObj = { id: 987654 };
+ });
+
+ afterEach(() => {
+ boardsStore.clearMultiSelect();
+ });
+
+ it('adds issue when not present', () => {
+ boardsStore.toggleMultiSelect(basicIssueObj);
+
+ const selectedIds = boardsStore.multiSelect.list.map(x => x.id);
+
+ expect(selectedIds.includes(basicIssueObj.id)).toEqual(true);
+ });
+
+ it('removes issue when issue is present', () => {
+ boardsStore.toggleMultiSelect(basicIssueObj);
+ let selectedIds = boardsStore.multiSelect.list.map(x => x.id);
+
+ expect(selectedIds.includes(basicIssueObj.id)).toEqual(true);
+
+ boardsStore.toggleMultiSelect(basicIssueObj);
+ selectedIds = boardsStore.multiSelect.list.map(x => x.id);
+
+ expect(selectedIds.includes(basicIssueObj.id)).toEqual(false);
+ });
+ });
+
+ describe('clearMultiSelect', () => {
+ it('clears all the multi selected issues', () => {
+ const issue1 = { id: 12345 };
+ const issue2 = { id: 12346 };
+
+ boardsStore.toggleMultiSelect(issue1);
+ boardsStore.toggleMultiSelect(issue2);
+
+ expect(boardsStore.multiSelect.list.length).toEqual(2);
+
+ boardsStore.clearMultiSelect();
+
+ expect(boardsStore.multiSelect.list.length).toEqual(0);
+ });
+ });
+
+ describe('moveMultipleIssuesToList', () => {
+ it('move issues on the new index', done => {
+ const listOne = boardsStore.addList(listObj);
+ const listTwo = boardsStore.addList(listObjDuplicate);
+
+ expect(boardsStore.state.lists.length).toBe(2);
+
+ setTimeout(() => {
+ expect(listOne.issues.length).toBe(1);
+ expect(listTwo.issues.length).toBe(1);
+
+ boardsStore.moveMultipleIssuesToList({
+ listFrom: listOne,
+ listTo: listTwo,
+ issues: listOne.issues,
+ newIndex: 0,
+ });
+
+ expect(listTwo.issues.length).toBe(1);
+
+ done();
+ }, 0);
+ });
+ });
+
+ describe('moveMultipleIssuesInList', () => {
+ it('moves multiple issues in list', done => {
+ const issueObj = {
+ title: 'Issue #1',
+ id: 12345,
+ iid: 2,
+ confidential: false,
+ labels: [],
+ assignees: [],
+ };
+ const issue1 = new ListIssue(issueObj);
+ const issue2 = new ListIssue({
+ ...issueObj,
+ title: 'Issue #2',
+ id: 12346,
+ });
+
+ const list = boardsStore.addList(listObj);
+
+ waitForPromises()
+ .then(() => {
+ list.addIssue(issue1);
+ list.addIssue(issue2);
+
+ expect(list.issues.length).toBe(3);
+ expect(list.issues[0].id).not.toBe(issue2.id);
+
+ boardsStore.moveMultipleIssuesInList({
+ list,
+ issues: [issue1, issue2],
+ oldIndicies: [0],
+ newIndex: 1,
+ idArray: [1, 12345, 12346],
+ });
+
+ expect(list.issues[0].id).toBe(issue1.id);
+
+ expect(gl.boardService.moveMultipleIssues).toHaveBeenCalledWith({
+ ids: [issue1.id, issue2.id],
+ fromListId: null,
+ toListId: null,
+ moveBeforeId: 1,
+ moveAfterId: null,
+ });
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index 8a20911cc66..314e051665e 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -32,7 +32,10 @@ describe('Issue card component', () => {
beforeEach(() => {
setFixtures('<div class="test-container"></div>');
- list = listObj;
+ list = {
+ ...listObj,
+ type: 'label',
+ };
issue = new ListIssue({
title: 'Testing',
id: 1,
@@ -42,6 +45,7 @@ describe('Issue card component', () => {
assignees: [],
reference_path: '#1',
real_path: '/test/1',
+ weight: 1,
});
component = new Vue({
@@ -240,8 +244,8 @@ describe('Issue card component', () => {
Vue.nextTick(() => done());
});
- it('renders list label', () => {
- expect(component.$el.querySelectorAll('.badge').length).toBe(2);
+ it('does not render list label but renders all other labels', () => {
+ expect(component.$el.querySelectorAll('.badge').length).toBe(1);
});
it('renders label', () => {
@@ -277,7 +281,7 @@ describe('Issue card component', () => {
Vue.nextTick()
.then(() => {
- expect(component.$el.querySelectorAll('.badge').length).toBe(2);
+ expect(component.$el.querySelectorAll('.badge').length).toBe(1);
expect(component.$el.textContent).not.toContain('closed');
done();
@@ -285,10 +289,4 @@ describe('Issue card component', () => {
.catch(done.fail);
});
});
-
- describe('weights', () => {
- it('not shows weight component', () => {
- expect(component.$el.querySelector('.board-card-weight')).toBeNull();
- });
- });
});
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index 50ad1442873..41b8f567e08 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -15,7 +15,7 @@ export const listObj = {
weight: 3,
label: {
id: 5000,
- title: 'Testing',
+ title: 'Test',
color: 'red',
description: 'testing;',
textColor: 'white',
@@ -30,7 +30,7 @@ export const listObjDuplicate = {
weight: 3,
label: {
id: listObj.label.id,
- title: 'Testing',
+ title: 'Test',
color: 'red',
description: 'testing;',
},
diff --git a/spec/javascripts/commit/commit_pipeline_status_component_spec.js b/spec/javascripts/commit/commit_pipeline_status_component_spec.js
deleted file mode 100644
index f6b36e88a5f..00000000000
--- a/spec/javascripts/commit/commit_pipeline_status_component_spec.js
+++ /dev/null
@@ -1,106 +0,0 @@
-import Vue from 'vue';
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('Commit pipeline status component', () => {
- let vm;
- let Component;
- let mock;
- const mockCiStatus = {
- details_path: '/root/hello-world/pipelines/1',
- favicon: 'canceled.ico',
- group: 'canceled',
- has_details: true,
- icon: 'status_canceled',
- label: 'canceled',
- text: 'canceled',
- };
-
- beforeEach(() => {
- Component = Vue.extend(commitPipelineStatus);
- });
-
- describe('While polling pipeline data successfully', () => {
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onGet('/dummy/endpoint').reply(() => {
- const res = Promise.resolve([
- 200,
- {
- pipelines: [
- {
- details: {
- status: mockCiStatus,
- },
- },
- ],
- },
- ]);
- return res;
- });
- vm = mountComponent(Component, {
- endpoint: '/dummy/endpoint',
- });
- });
-
- afterEach(() => {
- vm.poll.stop();
- vm.$destroy();
- mock.restore();
- });
-
- it('shows the loading icon when polling is starting', done => {
- expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
- setTimeout(() => {
- expect(vm.$el.querySelector('.loading-container')).toBe(null);
- done();
- });
- });
-
- it('contains a ciStatus when the polling is successful ', done => {
- setTimeout(() => {
- expect(vm.ciStatus).toEqual(mockCiStatus);
- done();
- });
- });
-
- it('contains a ci-status icon when polling is successful', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.ci-status-icon')).not.toBe(null);
- expect(vm.$el.querySelector('.ci-status-icon').classList).toContain(
- `ci-status-icon-${mockCiStatus.group}`,
- );
- done();
- });
- });
- });
-
- describe('When polling data was not successful', () => {
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onGet('/dummy/endpoint').reply(502, {});
- vm = new Component({
- props: {
- endpoint: '/dummy/endpoint',
- },
- });
- });
-
- afterEach(() => {
- vm.poll.stop();
- vm.$destroy();
- mock.restore();
- });
-
- it('calls an errorCallback', done => {
- spyOn(vm, 'errorCallback').and.callThrough();
- vm.$mount();
- setTimeout(() => {
- expect(vm.errorCallback.calls.count()).toEqual(1);
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/create_cluster/.eslintrc.yml b/spec/javascripts/create_cluster/.eslintrc.yml
new file mode 100644
index 00000000000..14e318a2f3e
--- /dev/null
+++ b/spec/javascripts/create_cluster/.eslintrc.yml
@@ -0,0 +1,3 @@
+rules:
+ # https://gitlab.com/gitlab-org/gitlab/issues/33025
+ promise/no-nesting: off
diff --git a/spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js b/spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js
index 809da3f9088..016ecfb35b8 100644
--- a/spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js
@@ -4,6 +4,7 @@ import { createStore } from '~/create_cluster/gke_cluster/store';
import { SET_PROJECTS } from '~/create_cluster/gke_cluster/store/mutation_types';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { emptyProjectMock, selectedProjectMock } from '../mock_data';
+import { gapi } from '../helpers';
const componentConfig = {
docsUrl: 'https://console.cloud.google.com/home/dashboard',
@@ -32,6 +33,16 @@ describe('GkeProjectIdDropdown', () => {
let vm;
let store;
+ let originalGapi;
+ beforeAll(() => {
+ originalGapi = window.gapi;
+ window.gapi = gapi();
+ });
+
+ afterAll(() => {
+ window.gapi = originalGapi;
+ });
+
beforeEach(() => {
store = createStore();
vm = createComponent(store);
diff --git a/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js b/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js
index a7591cc38c7..7ceaeace82f 100644
--- a/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js
@@ -64,7 +64,15 @@ describe('GCP Cluster Dropdown Store Actions', () => {
});
describe('async fetch methods', () => {
- window.gapi = gapi();
+ let originalGapi;
+ beforeAll(() => {
+ originalGapi = window.gapi;
+ window.gapi = gapi();
+ });
+
+ afterAll(() => {
+ window.gapi = originalGapi;
+ });
describe('fetchProjects', () => {
it('fetches projects from Google API', done => {
diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js
index bd8608b6bac..28fa87ac097 100644
--- a/spec/javascripts/flash_spec.js
+++ b/spec/javascripts/flash_spec.js
@@ -176,7 +176,7 @@ describe('Flash', () => {
it('removes element after clicking', () => {
flash('test', 'alert', document, null, false, true);
- document.querySelector('.flash-alert').click();
+ document.querySelector('.flash-alert .js-close-icon').click();
expect(document.querySelector('.flash-alert')).toBeNull();
@@ -210,7 +210,13 @@ describe('Flash', () => {
describe('removeFlashClickListener', () => {
beforeEach(() => {
- document.body.innerHTML += '<div class="flash-container"><div class="flash"></div></div>';
+ document.body.innerHTML += `
+ <div class="flash-container">
+ <div class="flash">
+ <div class="close-icon js-close-icon"></div>
+ </div>
+ </div>
+ `;
});
it('removes global flash on click', done => {
@@ -218,7 +224,7 @@ describe('Flash', () => {
removeFlashClickListener(flashEl, false);
- flashEl.click();
+ flashEl.querySelector('.js-close-icon').click();
setTimeout(() => {
expect(document.querySelector('.flash')).toBeNull();
diff --git a/spec/javascripts/frequent_items/components/app_spec.js b/spec/javascripts/frequent_items/components/app_spec.js
index 6814f656f5d..36dd8604d08 100644
--- a/spec/javascripts/frequent_items/components/app_spec.js
+++ b/spec/javascripts/frequent_items/components/app_spec.js
@@ -236,8 +236,15 @@ describe('Frequent Items App Component', () => {
.then(() => {
expect(vm.$el.querySelector('.loading-animation')).toBeDefined();
})
+
+ // This test waits for multiple ticks in order to allow the responses to
+ // propagate through each interceptor installed on the Axios instance.
+ // This shouldn't be necessary; this test should be refactored to avoid this.
+ // https://gitlab.com/gitlab-org/gitlab/issues/32479
+ .then(vm.$nextTick)
.then(vm.$nextTick)
.then(vm.$nextTick)
+
.then(() => {
expect(vm.$el.querySelectorAll('.frequent-items-list-container li').length).toBe(
mockSearchedProjects.length,
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index 0ddf589f368..c36d3be1b22 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -20,26 +20,26 @@ describe('Header', function() {
});
it('should update todos-count after receiving the todo:toggle event', () => {
- triggerToggle('5');
+ triggerToggle(5);
expect($(todosPendingCount).text()).toEqual('5');
});
it('should hide todos-count when it is 0', () => {
- triggerToggle('0');
+ triggerToggle(0);
expect(isTodosCountHidden()).toEqual(true);
});
it('should show todos-count when it is more than 0', () => {
- triggerToggle('10');
+ triggerToggle(10);
expect(isTodosCountHidden()).toEqual(false);
});
describe('when todos-count is 1000', () => {
beforeEach(() => {
- triggerToggle('1000');
+ triggerToggle(1000);
});
it('should show todos-count', () => {
diff --git a/spec/javascripts/helpers/tracking_helper.js b/spec/javascripts/helpers/tracking_helper.js
new file mode 100644
index 00000000000..68c1bd2dbca
--- /dev/null
+++ b/spec/javascripts/helpers/tracking_helper.js
@@ -0,0 +1,25 @@
+import Tracking from '~/tracking';
+
+export default Tracking;
+
+let document;
+let handlers;
+
+export function mockTracking(category = '_category_', documentOverride, spyMethod) {
+ document = documentOverride || window.document;
+ window.snowplow = () => {};
+ Tracking.bindDocument(category, document);
+ return spyMethod ? spyMethod(Tracking, 'event') : null;
+}
+
+export function unmockTracking() {
+ window.snowplow = undefined;
+ handlers.forEach(event => document.removeEventListener(event.name, event.func));
+}
+
+export function triggerEvent(selectorOrEl, eventName = 'click') {
+ const event = new Event(eventName, { bubbles: true });
+ const el = typeof selectorOrEl === 'string' ? document.querySelector(selectorOrEl) : selectorOrEl;
+
+ el.dispatchEvent(event);
+}
diff --git a/spec/javascripts/helpers/vue_resource_helper.js b/spec/javascripts/helpers/vue_resource_helper.js
deleted file mode 100644
index 0f58af09933..00000000000
--- a/spec/javascripts/helpers/vue_resource_helper.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// eslint-disable-next-line import/prefer-default-export
-export const headersInterceptor = (request, next) => {
- next(response => {
- const headers = {};
- response.headers.forEach((value, key) => {
- headers[key] = value;
- });
- // eslint-disable-next-line no-param-reassign
- response.headers = headers;
- });
-};
diff --git a/spec/javascripts/ide/components/branches/search_list_spec.js b/spec/javascripts/ide/components/branches/search_list_spec.js
deleted file mode 100644
index 72a3c2d5dcd..00000000000
--- a/spec/javascripts/ide/components/branches/search_list_spec.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import * as types from '~/ide/stores/modules/branches/mutation_types';
-import List from '~/ide/components/branches/search_list.vue';
-import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
-import { branches as testBranches } from '../../mock_data';
-import { resetStore } from '../../helpers';
-
-describe('IDE branches search list', () => {
- const Component = Vue.extend(List);
- let vm;
-
- beforeEach(() => {
- vm = createComponentWithStore(Component, store, {});
-
- spyOn(vm, 'fetchBranches');
-
- vm.$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(store);
- });
-
- it('calls fetch on mounted', () => {
- expect(vm.fetchBranches).toHaveBeenCalledWith({
- search: '',
- });
- });
-
- it('renders loading icon', done => {
- vm.$store.state.branches.isLoading = true;
-
- vm.$nextTick()
- .then(() => {
- expect(vm.$el).toContainElement('.loading-container');
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('renders branches not found when search is not empty', done => {
- vm.search = 'testing';
-
- vm.$nextTick(() => {
- expect(vm.$el).toContainText('No branches found');
-
- done();
- });
- });
-
- describe('with branches', () => {
- const currentBranch = testBranches[1];
-
- beforeEach(done => {
- vm.$store.state.currentBranchId = currentBranch.name;
- vm.$store.commit(`branches/${types.RECEIVE_BRANCHES_SUCCESS}`, testBranches);
-
- vm.$nextTick(done);
- });
-
- it('renders list', () => {
- const elementText = Array.from(vm.$el.querySelectorAll('li strong')).map(x =>
- x.textContent.trim(),
- );
-
- expect(elementText).toEqual(testBranches.map(x => x.name));
- });
-
- it('renders check next to active branch', () => {
- const checkedText = Array.from(vm.$el.querySelectorAll('li'))
- .filter(x => x.querySelector('.ide-search-list-current-icon svg'))
- .map(x => x.querySelector('strong').textContent.trim());
-
- expect(checkedText).toEqual([currentBranch.name]);
- });
- });
-});
diff --git a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
index bf48d7bfdad..c1dcd4928a0 100644
--- a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js
@@ -2,12 +2,14 @@ import Vue from 'vue';
import store from '~/ide/stores';
import listItem from '~/ide/components/commit_sidebar/list_item.vue';
import router from '~/ide/ide_router';
+import { trimText } from 'spec/helpers/text_helper';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { file, resetStore } from '../../helpers';
describe('Multi-file editor commit sidebar list item', () => {
let vm;
let f;
+ let findPathEl;
beforeEach(() => {
const Component = Vue.extend(listItem);
@@ -21,6 +23,8 @@ describe('Multi-file editor commit sidebar list item', () => {
actionComponent: 'stage-button',
activeFileKey: `staged-${f.key}`,
}).$mount();
+
+ findPathEl = vm.$el.querySelector('.multi-file-commit-list-path');
});
afterEach(() => {
@@ -29,15 +33,39 @@ describe('Multi-file editor commit sidebar list item', () => {
resetStore(store);
});
+ const findPathText = () => trimText(findPathEl.textContent);
+
it('renders file path', () => {
- expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent).toContain(f.path);
+ expect(findPathText()).toContain(f.path);
+ });
+
+ it('correctly renders renamed entries', done => {
+ Vue.set(vm.file, 'prevName', 'Old name');
+
+ vm.$nextTick()
+ .then(() => {
+ expect(findPathText()).toEqual(`Old name → ${f.name}`);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('correctly renders entry, the name of which did not change after rename (as within a folder)', done => {
+ Vue.set(vm.file, 'prevName', f.name);
+
+ vm.$nextTick()
+ .then(() => {
+ expect(findPathText()).toEqual(f.name);
+ })
+ .then(done)
+ .catch(done.fail);
});
it('opens a closed file in the editor when clicking the file path', done => {
spyOn(vm, 'openPendingTab').and.callThrough();
spyOn(router, 'push');
- vm.$el.querySelector('.multi-file-commit-list-path').click();
+ findPathEl.click();
setTimeout(() => {
expect(vm.openPendingTab).toHaveBeenCalled();
@@ -52,7 +80,7 @@ describe('Multi-file editor commit sidebar list item', () => {
spyOn(vm, 'updateViewer').and.callThrough();
spyOn(router, 'push');
- vm.$el.querySelector('.multi-file-commit-list-path').click();
+ findPathEl.click();
setTimeout(() => {
expect(vm.updateViewer).toHaveBeenCalledWith('diff');
diff --git a/spec/javascripts/ide/components/error_message_spec.js b/spec/javascripts/ide/components/error_message_spec.js
deleted file mode 100644
index 80d6c7fd564..00000000000
--- a/spec/javascripts/ide/components/error_message_spec.js
+++ /dev/null
@@ -1,106 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import ErrorMessage from '~/ide/components/error_message.vue';
-import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
-import { resetStore } from '../helpers';
-
-describe('IDE error message component', () => {
- const Component = Vue.extend(ErrorMessage);
- let vm;
-
- beforeEach(() => {
- vm = createComponentWithStore(Component, store, {
- message: {
- text: 'error message',
- action: null,
- actionText: null,
- },
- }).$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
- resetStore(vm.$store);
- });
-
- it('renders error message', () => {
- expect(vm.$el.textContent).toContain('error message');
- });
-
- it('clears error message on click', () => {
- spyOn(vm, 'setErrorMessage');
-
- vm.$el.click();
-
- expect(vm.setErrorMessage).toHaveBeenCalledWith(null);
- });
-
- describe('with action', () => {
- let actionSpy;
-
- beforeEach(done => {
- actionSpy = jasmine.createSpy('action').and.returnValue(Promise.resolve());
-
- vm.message.action = actionSpy;
- vm.message.actionText = 'test action';
- vm.message.actionPayload = 'testActionPayload';
-
- vm.$nextTick(done);
- });
-
- it('renders action button', () => {
- expect(vm.$el.querySelector('.flash-action')).not.toBe(null);
- expect(vm.$el.textContent).toContain('test action');
- });
-
- it('does not clear error message on click', () => {
- spyOn(vm, 'setErrorMessage');
-
- vm.$el.click();
-
- expect(vm.setErrorMessage).not.toHaveBeenCalled();
- });
-
- it('dispatches action', done => {
- vm.$el.querySelector('.flash-action').click();
-
- vm.$nextTick(() => {
- expect(actionSpy).toHaveBeenCalledWith('testActionPayload');
-
- done();
- });
- });
-
- it('does not dispatch action when already loading', () => {
- vm.isLoading = true;
-
- vm.$el.querySelector('.flash-action').click();
-
- expect(actionSpy).not.toHaveBeenCalledWith();
- });
-
- it('resets isLoading after click', done => {
- vm.$el.querySelector('.flash-action').click();
-
- expect(vm.isLoading).toBe(true);
-
- setTimeout(() => {
- expect(vm.isLoading).toBe(false);
-
- done();
- });
- });
-
- it('shows loading icon when isLoading is true', done => {
- expect(vm.$el.querySelector('.loading-container').style.display).not.toBe('');
-
- vm.isLoading = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.loading-container').style.display).toBe('');
-
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/ide/components/file_row_extra_spec.js b/spec/javascripts/ide/components/file_row_extra_spec.js
index d7fed3f0681..86146fcef69 100644
--- a/spec/javascripts/ide/components/file_row_extra_spec.js
+++ b/spec/javascripts/ide/components/file_row_extra_spec.js
@@ -139,6 +139,27 @@ describe('IDE extra file row component', () => {
done();
});
});
+
+ it('shows when file is renamed', done => {
+ vm.file.prevPath = 'original-file';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null);
+
+ done();
+ });
+ });
+
+ it('hides when file is renamed', done => {
+ vm.file.prevPath = 'original-file';
+ vm.file.type = 'tree';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.file-changed-icon')).toBe(null);
+
+ done();
+ });
+ });
});
describe('merge request icon', () => {
diff --git a/spec/javascripts/ide/components/file_templates/dropdown_spec.js b/spec/javascripts/ide/components/file_templates/dropdown_spec.js
deleted file mode 100644
index 898796f4fa0..00000000000
--- a/spec/javascripts/ide/components/file_templates/dropdown_spec.js
+++ /dev/null
@@ -1,201 +0,0 @@
-import $ from 'jquery';
-import Vue from 'vue';
-import { createStore } from '~/ide/stores';
-import Dropdown from '~/ide/components/file_templates/dropdown.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { resetStore } from '../../helpers';
-
-describe('IDE file templates dropdown component', () => {
- let Component;
- let vm;
-
- beforeAll(() => {
- Component = Vue.extend(Dropdown);
- });
-
- beforeEach(() => {
- const store = createStore();
-
- vm = createComponentWithStore(Component, store, {
- label: 'Test',
- }).$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
- resetStore(vm.$store);
- });
-
- describe('async', () => {
- beforeEach(() => {
- vm.isAsyncData = true;
- });
-
- it('calls async store method on Bootstrap dropdown event', () => {
- spyOn(vm, 'fetchTemplateTypes').and.stub();
-
- $(vm.$el).trigger('show.bs.dropdown');
-
- expect(vm.fetchTemplateTypes).toHaveBeenCalled();
- });
-
- it('renders templates when async', done => {
- vm.$store.state.fileTemplates.templates = [
- {
- name: 'test',
- },
- ];
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dropdown-content').textContent).toContain('test');
-
- done();
- });
- });
-
- it('renders loading icon when isLoading is true', done => {
- vm.$store.state.fileTemplates.isLoading = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
-
- done();
- });
- });
-
- it('searches template data', () => {
- vm.$store.state.fileTemplates.templates = [
- {
- name: 'test',
- },
- ];
- vm.searchable = true;
- vm.search = 'hello';
-
- expect(vm.outputData).toEqual([]);
- });
-
- it('does not filter data is searchable is false', () => {
- vm.$store.state.fileTemplates.templates = [
- {
- name: 'test',
- },
- ];
- vm.search = 'hello';
-
- expect(vm.outputData).toEqual([
- {
- name: 'test',
- },
- ]);
- });
-
- it('calls clickItem on click', done => {
- spyOn(vm, 'clickItem').and.stub();
-
- vm.$store.state.fileTemplates.templates = [
- {
- name: 'test',
- },
- ];
-
- vm.$nextTick(() => {
- vm.$el.querySelector('.dropdown-content button').click();
-
- expect(vm.clickItem).toHaveBeenCalledWith({
- name: 'test',
- });
-
- done();
- });
- });
-
- it('renders input when searchable is true', done => {
- vm.searchable = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dropdown-input')).not.toBe(null);
-
- done();
- });
- });
-
- it('does not render input when searchable is true & showLoading is true', done => {
- vm.searchable = true;
- vm.$store.state.fileTemplates.isLoading = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dropdown-input')).toBe(null);
-
- done();
- });
- });
- });
-
- describe('sync', () => {
- beforeEach(done => {
- vm.data = [
- {
- name: 'test sync',
- },
- ];
-
- vm.$nextTick(done);
- });
-
- it('renders props data', () => {
- expect(vm.$el.querySelector('.dropdown-content').textContent).toContain('test sync');
- });
-
- it('renders input when searchable is true', done => {
- vm.searchable = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dropdown-input')).not.toBe(null);
-
- done();
- });
- });
-
- it('calls clickItem on click', done => {
- spyOn(vm, 'clickItem').and.stub();
-
- vm.$nextTick(() => {
- vm.$el.querySelector('.dropdown-content button').click();
-
- expect(vm.clickItem).toHaveBeenCalledWith({
- name: 'test sync',
- });
-
- done();
- });
- });
-
- it('searches template data', () => {
- vm.searchable = true;
- vm.search = 'hello';
-
- expect(vm.outputData).toEqual([]);
- });
-
- it('does not filter data is searchable is false', () => {
- vm.search = 'hello';
-
- expect(vm.outputData).toEqual([
- {
- name: 'test sync',
- },
- ]);
- });
-
- it('renders dropdown title', done => {
- vm.title = 'Test title';
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dropdown-title').textContent).toContain('Test title');
-
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/ide/components/ide_tree_list_spec.js b/spec/javascripts/ide/components/ide_tree_list_spec.js
index 554bd1ae3b5..f63007c7dd2 100644
--- a/spec/javascripts/ide/components/ide_tree_list_spec.js
+++ b/spec/javascripts/ide/components/ide_tree_list_spec.js
@@ -58,20 +58,6 @@ describe('IDE tree list', () => {
it('renders list of files', () => {
expect(vm.$el.textContent).toContain('fileName');
});
-
- it('does not render moved entries', done => {
- const tree = [file('moved entry'), file('normal entry')];
- tree[0].moved = true;
- store.state.trees['abcproject/master'].tree = tree;
- const container = vm.$el.querySelector('.ide-tree-body');
-
- vm.$nextTick(() => {
- expect(container.children.length).toBe(1);
- expect(vm.$el.textContent).not.toContain('moved entry');
- expect(vm.$el.textContent).toContain('normal entry');
- done();
- });
- });
});
describe('empty-branch state', () => {
diff --git a/spec/javascripts/ide/components/jobs/list_spec.js b/spec/javascripts/ide/components/jobs/list_spec.js
deleted file mode 100644
index b24853c56fa..00000000000
--- a/spec/javascripts/ide/components/jobs/list_spec.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import Vue from 'vue';
-import StageList from '~/ide/components/jobs/list.vue';
-import { createStore } from '~/ide/stores';
-import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
-import { stages, jobs } from '../../mock_data';
-
-describe('IDE stages list', () => {
- const Component = Vue.extend(StageList);
- let vm;
-
- beforeEach(() => {
- const store = createStore();
-
- vm = createComponentWithStore(Component, store, {
- stages: stages.map((mappedState, i) => ({
- ...mappedState,
- id: i,
- dropdownPath: mappedState.dropdown_path,
- jobs: [...jobs],
- isLoading: false,
- isCollapsed: false,
- })),
- loading: false,
- });
-
- spyOn(vm, 'fetchJobs');
- spyOn(vm, 'toggleStageCollapsed');
-
- vm.$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders list of stages', () => {
- expect(vm.$el.querySelectorAll('.card').length).toBe(2);
- });
-
- it('renders loading icon when no stages & is loading', done => {
- vm.stages = [];
- vm.loading = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
-
- done();
- });
- });
-
- it('calls toggleStageCollapsed when clicking stage header', done => {
- vm.$el.querySelector('.card-header').click();
-
- vm.$nextTick(() => {
- expect(vm.toggleStageCollapsed).toHaveBeenCalledWith(0);
-
- done();
- });
- });
-
- it('calls fetchJobs when stage is mounted', () => {
- expect(vm.fetchJobs.calls.count()).toBe(stages.length);
-
- expect(vm.fetchJobs.calls.argsFor(0)).toEqual([vm.stages[0]]);
- expect(vm.fetchJobs.calls.argsFor(1)).toEqual([vm.stages[1]]);
- });
-});
diff --git a/spec/javascripts/ide/components/merge_requests/list_spec.js b/spec/javascripts/ide/components/merge_requests/list_spec.js
deleted file mode 100644
index 55e4f46d9ca..00000000000
--- a/spec/javascripts/ide/components/merge_requests/list_spec.js
+++ /dev/null
@@ -1,159 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import List from '~/ide/components/merge_requests/list.vue';
-import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
-import { mergeRequests } from '../../mock_data';
-import { resetStore } from '../../helpers';
-
-describe('IDE merge requests list', () => {
- const Component = Vue.extend(List);
- let vm;
-
- beforeEach(() => {
- vm = createComponentWithStore(Component, store, {});
-
- spyOn(vm, 'fetchMergeRequests');
-
- vm.$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- it('calls fetch on mounted', () => {
- expect(vm.fetchMergeRequests).toHaveBeenCalledWith({
- search: '',
- type: '',
- });
- });
-
- it('renders loading icon', done => {
- vm.$store.state.mergeRequests.isLoading = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
-
- done();
- });
- });
-
- it('renders no search results text when search is not empty', done => {
- vm.search = 'testing';
-
- vm.$nextTick(() => {
- expect(vm.$el.textContent).toContain('No merge requests found');
-
- done();
- });
- });
-
- it('clicking on search type, sets currentSearchType and loads merge requests', done => {
- vm.onSearchFocus();
-
- vm.$nextTick()
- .then(() => {
- vm.$el.querySelector('li button').click();
-
- return vm.$nextTick();
- })
- .then(() => {
- expect(vm.currentSearchType).toEqual(vm.$options.searchTypes[0]);
- expect(vm.fetchMergeRequests).toHaveBeenCalledWith({
- type: vm.currentSearchType.type,
- search: '',
- });
- })
- .then(done)
- .catch(done.fail);
- });
-
- describe('with merge requests', () => {
- beforeEach(done => {
- vm.$store.state.mergeRequests.mergeRequests.push({
- ...mergeRequests[0],
- projectPathWithNamespace: 'gitlab-org/gitlab-ce',
- });
-
- vm.$nextTick(done);
- });
-
- it('renders list', () => {
- expect(vm.$el.querySelectorAll('li').length).toBe(1);
- expect(vm.$el.querySelector('li').textContent).toContain(mergeRequests[0].title);
- });
- });
-
- describe('searchMergeRequests', () => {
- beforeEach(() => {
- spyOn(vm, 'loadMergeRequests');
-
- jasmine.clock().install();
- });
-
- afterEach(() => {
- jasmine.clock().uninstall();
- });
-
- it('calls loadMergeRequests on input in search field', () => {
- const event = new Event('input');
-
- vm.$el.querySelector('input').dispatchEvent(event);
-
- jasmine.clock().tick(300);
-
- expect(vm.loadMergeRequests).toHaveBeenCalled();
- });
- });
-
- describe('onSearchFocus', () => {
- it('shows search types', done => {
- vm.$el.querySelector('input').dispatchEvent(new Event('focus'));
-
- expect(vm.hasSearchFocus).toBe(true);
- expect(vm.showSearchTypes).toBe(true);
-
- vm.$nextTick()
- .then(() => {
- const expectedSearchTypes = vm.$options.searchTypes.map(x => x.label);
- const renderedSearchTypes = Array.from(vm.$el.querySelectorAll('li')).map(x =>
- x.textContent.trim(),
- );
-
- expect(renderedSearchTypes).toEqual(expectedSearchTypes);
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('does not show search types, if already has search value', () => {
- vm.search = 'lorem ipsum';
- vm.$el.querySelector('input').dispatchEvent(new Event('focus'));
-
- expect(vm.hasSearchFocus).toBe(true);
- expect(vm.showSearchTypes).toBe(false);
- });
-
- it('does not show search types, if already has a search type', () => {
- vm.currentSearchType = {};
- vm.$el.querySelector('input').dispatchEvent(new Event('focus'));
-
- expect(vm.hasSearchFocus).toBe(true);
- expect(vm.showSearchTypes).toBe(false);
- });
-
- it('resets hasSearchFocus when search changes', done => {
- vm.hasSearchFocus = true;
- vm.search = 'something else';
-
- vm.$nextTick()
- .then(() => {
- expect(vm.hasSearchFocus).toBe(false);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/ide/components/pipelines/list_spec.js b/spec/javascripts/ide/components/pipelines/list_spec.js
deleted file mode 100644
index 80829f2358a..00000000000
--- a/spec/javascripts/ide/components/pipelines/list_spec.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import Vue from 'vue';
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import { createStore } from '~/ide/stores';
-import List from '~/ide/components/pipelines/list.vue';
-import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
-import { pipelines, projectData, stages, jobs } from '../../mock_data';
-
-describe('IDE pipelines list', () => {
- const Component = Vue.extend(List);
- let vm;
- let mock;
-
- const findLoadingState = () => vm.$el.querySelector('.loading-container');
-
- beforeEach(done => {
- const store = createStore();
-
- mock = new MockAdapter(axios);
-
- store.state.currentProjectId = 'abc/def';
- store.state.currentBranchId = 'master';
- store.state.projects['abc/def'] = {
- ...projectData,
- path_with_namespace: 'abc/def',
- branches: {
- master: { commit: { id: '123' } },
- },
- };
- store.state.links = { ciHelpPagePath: gl.TEST_HOST };
- store.state.pipelinesEmptyStateSvgPath = gl.TEST_HOST;
- store.state.pipelines.stages = stages.map((mappedState, i) => ({
- ...mappedState,
- id: i,
- dropdownPath: mappedState.dropdown_path,
- jobs: [...jobs],
- isLoading: false,
- isCollapsed: false,
- }));
-
- mock
- .onGet('/abc/def/commit/123/pipelines')
- .replyOnce(200, { pipelines: [...pipelines] }, { 'poll-interval': '-1' });
-
- vm = createComponentWithStore(Component, store).$mount();
-
- setTimeout(done);
- });
-
- afterEach(done => {
- vm.$destroy();
- mock.restore();
-
- vm.$store
- .dispatch('pipelines/stopPipelinePolling')
- .then(() => vm.$store.dispatch('pipelines/clearEtagPoll'))
- .then(done)
- .catch(done.fail);
- });
-
- it('renders pipeline data', () => {
- expect(vm.$el.textContent).toContain('#1');
- });
-
- it('renders CI icon', () => {
- expect(vm.$el.querySelector('.ci-status-icon-failed')).not.toBe(null);
- });
-
- it('renders list of jobs', () => {
- expect(vm.$el.querySelectorAll('.tab-pane:first-child .ide-job-item').length).toBe(
- jobs.length * stages.length,
- );
- });
-
- it('renders list of failed jobs on failed jobs tab', done => {
- vm.$el.querySelectorAll('.tab-links a')[1].click();
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelectorAll('.tab-pane.active .ide-job-item').length).toBe(2);
-
- done();
- });
- });
-
- describe('YAML error', () => {
- it('renders YAML error', done => {
- vm.$store.state.pipelines.latestPipeline.yamlError = 'test yaml error';
-
- vm.$nextTick(() => {
- expect(vm.$el.textContent).toContain('Found errors in your .gitlab-ci.yml:');
- expect(vm.$el.textContent).toContain('test yaml error');
-
- done();
- });
- });
- });
-
- describe('empty state', () => {
- it('renders pipelines empty state', done => {
- vm.$store.state.pipelines.latestPipeline = null;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.empty-state')).not.toBe(null);
-
- done();
- });
- });
- });
-
- describe('loading state', () => {
- beforeEach(() => {
- vm.$store.state.pipelines.isLoadingPipeline = true;
- });
-
- it('does not render when pipeline has loaded before', done => {
- vm.$store.state.pipelines.hasLoadedPipeline = true;
-
- vm.$nextTick()
- .then(() => {
- expect(findLoadingState()).toBe(null);
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('renders loading state when there is no latest pipeline', done => {
- vm.$store.state.pipelines.hasLoadedPipeline = false;
-
- vm.$nextTick()
- .then(() => {
- expect(findLoadingState()).not.toBe(null);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
index 0701b773e17..d1b43df74b9 100644
--- a/spec/javascripts/ide/components/repo_editor_spec.js
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -410,10 +410,23 @@ describe('RepoEditor', () => {
describe('initEditor', () => {
beforeEach(() => {
+ vm.file.tempFile = false;
spyOn(vm.editor, 'createInstance');
spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true);
});
+ it('does not fetch file information for temp entries', done => {
+ vm.file.tempFile = true;
+
+ vm.initEditor();
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.getFileData).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
it('is being initialised for files without content even if shouldHideEditor is `true`', done => {
vm.file.content = '';
vm.file.raw = '';
@@ -429,16 +442,13 @@ describe('RepoEditor', () => {
});
it('does not initialize editor for files already with content', done => {
- expect(vm.getFileData.calls.count()).toEqual(1);
- expect(vm.getRawFileData.calls.count()).toEqual(1);
-
vm.file.content = 'foo';
vm.initEditor();
vm.$nextTick()
.then(() => {
- expect(vm.getFileData.calls.count()).toEqual(1);
- expect(vm.getRawFileData.calls.count()).toEqual(1);
+ expect(vm.getFileData).not.toHaveBeenCalled();
+ expect(vm.getRawFileData).not.toHaveBeenCalled();
expect(vm.editor.createInstance).not.toHaveBeenCalled();
})
.then(done)
@@ -446,23 +456,56 @@ describe('RepoEditor', () => {
});
});
- it('calls removePendingTab when old file is pending', done => {
- spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true);
- spyOn(vm, 'removePendingTab');
+ describe('updates on file changes', () => {
+ beforeEach(() => {
+ spyOn(vm, 'initEditor');
+ });
- vm.file.pending = true;
+ it('calls removePendingTab when old file is pending', done => {
+ spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true);
+ spyOn(vm, 'removePendingTab');
- vm.$nextTick()
- .then(() => {
- vm.file = file('testing');
- vm.file.content = 'foo'; // need to prevent full cycle of initEditor
+ vm.file.pending = true;
+
+ vm.$nextTick()
+ .then(() => {
+ vm.file = file('testing');
+ vm.file.content = 'foo'; // need to prevent full cycle of initEditor
- return vm.$nextTick();
- })
- .then(() => {
- expect(vm.removePendingTab).toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
+ return vm.$nextTick();
+ })
+ .then(() => {
+ expect(vm.removePendingTab).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not call initEditor if the file did not change', done => {
+ Vue.set(vm, 'file', vm.file);
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.initEditor).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('calls initEditor when file key is changed', done => {
+ expect(vm.initEditor).not.toHaveBeenCalled();
+
+ Vue.set(vm, 'file', {
+ ...vm.file,
+ key: 'new',
+ });
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.initEditor).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
});
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index c02c7e5d45e..27f0ad01f54 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -1,226 +1 @@
-export const projectData = {
- id: 1,
- name: 'abcproject',
- web_url: '',
- avatar_url: '',
- path: '',
- name_with_namespace: 'namespace/abcproject',
- branches: {
- master: {
- treeId: 'abcproject/master',
- can_push: true,
- commit: {
- id: '123',
- },
- },
- },
- mergeRequests: {},
- merge_requests_enabled: true,
- default_branch: 'master',
-};
-
-export const pipelines = [
- {
- id: 1,
- ref: 'master',
- sha: '123',
- details: {
- status: {
- icon: 'status_failed',
- group: 'failed',
- text: 'Failed',
- },
- },
- commit: { id: '123' },
- },
- {
- id: 2,
- ref: 'master',
- sha: '213',
- details: {
- status: {
- icon: 'status_failed',
- group: 'failed',
- text: 'Failed',
- },
- },
- commit: { id: '213' },
- },
-];
-
-export const stages = [
- {
- dropdown_path: `${gl.TEST_HOST}/testing`,
- name: 'build',
- status: {
- icon: 'status_failed',
- group: 'failed',
- text: 'failed',
- },
- },
- {
- dropdown_path: 'testing',
- name: 'test',
- status: {
- icon: 'status_failed',
- group: 'failed',
- text: 'failed',
- },
- },
-];
-
-export const jobs = [
- {
- id: 1,
- name: 'test',
- path: 'testing',
- status: {
- icon: 'status_success',
- text: 'passed',
- },
- stage: 'test',
- duration: 1,
- started: new Date(),
- },
- {
- id: 2,
- name: 'test 2',
- path: 'testing2',
- status: {
- icon: 'status_success',
- text: 'passed',
- },
- stage: 'test',
- duration: 1,
- started: new Date(),
- },
- {
- id: 3,
- name: 'test 3',
- path: 'testing3',
- status: {
- icon: 'status_success',
- text: 'passed',
- },
- stage: 'test',
- duration: 1,
- started: new Date(),
- },
- {
- id: 4,
- name: 'test 4',
- path: 'testing4',
- status: {
- icon: 'status_failed',
- text: 'failed',
- },
- stage: 'build',
- duration: 1,
- started: new Date(),
- },
-];
-
-export const fullPipelinesResponse = {
- data: {
- count: {
- all: 2,
- },
- pipelines: [
- {
- id: '51',
- path: 'test',
- commit: {
- id: '123',
- },
- details: {
- status: {
- icon: 'status_failed',
- text: 'failed',
- },
- stages: [...stages],
- },
- },
- {
- id: '50',
- commit: {
- id: 'abc123def456ghi789jkl',
- },
- details: {
- status: {
- icon: 'status_success',
- text: 'passed',
- },
- stages: [...stages],
- },
- },
- ],
- },
-};
-
-export const mergeRequests = [
- {
- id: 1,
- iid: 1,
- title: 'Test merge request',
- project_id: 1,
- web_url: `${gl.TEST_HOST}/namespace/project-path/merge_requests/1`,
- },
-];
-
-export const branches = [
- {
- id: 1,
- name: 'master',
- commit: {
- message: 'Update master branch',
- committed_date: '2018-08-01T00:20:05Z',
- },
- can_push: true,
- protected: true,
- default: true,
- },
- {
- id: 2,
- name: 'protected/no-access',
- commit: {
- message: 'Update some stuff',
- committed_date: '2018-08-02T00:00:05Z',
- },
- can_push: false,
- protected: true,
- default: false,
- },
- {
- id: 3,
- name: 'protected/access',
- commit: {
- message: 'Update some stuff',
- committed_date: '2018-08-02T00:00:05Z',
- },
- can_push: true,
- protected: true,
- default: false,
- },
- {
- id: 4,
- name: 'regular',
- commit: {
- message: 'Update some more stuff',
- committed_date: '2018-06-30T00:20:05Z',
- },
- can_push: true,
- protected: false,
- default: false,
- },
- {
- id: 5,
- name: 'regular/no-access',
- commit: {
- message: 'Update some more stuff',
- committed_date: '2018-06-30T00:20:05Z',
- },
- can_push: false,
- protected: false,
- default: false,
- },
-];
+export * from '../../frontend/ide/mock_data';
diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js
index 8ecb6129c63..bcc7b5d5e46 100644
--- a/spec/javascripts/ide/stores/actions/project_spec.js
+++ b/spec/javascripts/ide/stores/actions/project_spec.js
@@ -6,8 +6,10 @@ import {
createNewBranchFromDefault,
showEmptyState,
openBranch,
+ loadFile,
+ loadBranch,
} from '~/ide/stores/actions';
-import store from '~/ide/stores';
+import { createStore } from '~/ide/stores';
import service from '~/ide/services';
import api from '~/api';
import router from '~/ide/ide_router';
@@ -16,8 +18,10 @@ import testAction from '../../../helpers/vuex_action_helper';
describe('IDE store project actions', () => {
let mock;
+ let store;
beforeEach(() => {
+ store = createStore();
mock = new MockAdapter(axios);
store.state.projects['abc/def'] = {
@@ -231,28 +235,139 @@ describe('IDE store project actions', () => {
});
});
+ describe('loadFile', () => {
+ beforeEach(() => {
+ Object.assign(store.state, {
+ entries: {
+ foo: { pending: false },
+ 'foo/bar-pending': { pending: true },
+ 'foo/bar': { pending: false },
+ },
+ });
+ spyOn(store, 'dispatch');
+ });
+
+ it('does nothing, if basePath is not given', () => {
+ loadFile(store, { basePath: undefined });
+
+ expect(store.dispatch).not.toHaveBeenCalled();
+ });
+
+ it('handles tree entry action, if basePath is given and the entry is not pending', () => {
+ loadFile(store, { basePath: 'foo/bar/' });
+
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'handleTreeEntryAction',
+ store.state.entries['foo/bar'],
+ );
+ });
+
+ it('does not handle tree entry action, if entry is pending', () => {
+ loadFile(store, { basePath: 'foo/bar-pending/' });
+
+ expect(store.dispatch).not.toHaveBeenCalledWith('handleTreeEntryAction', jasmine.anything());
+ });
+
+ it('creates a new temp file supplied via URL if the file does not exist yet', () => {
+ loadFile(store, { basePath: 'not-existent.md' });
+
+ expect(store.dispatch.calls.count()).toBe(1);
+
+ expect(store.dispatch).not.toHaveBeenCalledWith('handleTreeEntryAction', jasmine.anything());
+
+ expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', {
+ name: 'not-existent.md',
+ type: 'blob',
+ });
+ });
+ });
+
+ describe('loadBranch', () => {
+ const projectId = 'abc/def';
+ const branchId = '123-lorem';
+
+ it('fetches branch data', done => {
+ spyOn(store, 'dispatch').and.returnValue(Promise.resolve());
+
+ loadBranch(store, { projectId, branchId })
+ .then(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([
+ ['getBranchData', { projectId, branchId }],
+ ['getMergeRequestsForBranch', { projectId, branchId }],
+ ['getFiles', { projectId, branchId }],
+ ]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('shows an error if branch can not be fetched', done => {
+ spyOn(store, 'dispatch').and.returnValue(Promise.reject());
+
+ loadBranch(store, { projectId, branchId })
+ .then(done.fail)
+ .catch(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([
+ ['getBranchData', { projectId, branchId }],
+ ['showBranchNotFoundError', branchId],
+ ]);
+ done();
+ });
+ });
+ });
+
describe('openBranch', () => {
+ const projectId = 'abc/def';
+ const branchId = '123-lorem';
+
const branch = {
- projectId: 'abc/def',
- branchId: '123-lorem',
+ projectId,
+ branchId,
};
beforeEach(() => {
- store.state.entries = {
- foo: { pending: false },
- 'foo/bar-pending': { pending: true },
- 'foo/bar': { pending: false },
- };
+ Object.assign(store.state, {
+ entries: {
+ foo: { pending: false },
+ 'foo/bar-pending': { pending: true },
+ 'foo/bar': { pending: false },
+ },
+ });
+ });
+
+ it('loads file right away if the branch has already been fetched', done => {
+ spyOn(store, 'dispatch');
+
+ Object.assign(store.state, {
+ projects: {
+ [projectId]: {
+ branches: {
+ [branchId]: { foo: 'bar' },
+ },
+ },
+ },
+ });
+
+ openBranch(store, branch)
+ .then(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([['loadFile', { basePath: undefined }]]);
+ })
+ .then(done)
+ .catch(done.fail);
});
describe('empty repo', () => {
beforeEach(() => {
spyOn(store, 'dispatch').and.returnValue(Promise.resolve());
- store.state.currentProjectId = 'abc/def';
- store.state.projects['abc/def'] = {
- empty_repo: true,
- };
+ Object.assign(store.state, {
+ currentProjectId: 'abc/def',
+ projects: {
+ 'abc/def': {
+ empty_repo: true,
+ },
+ },
+ });
});
afterEach(() => {
@@ -262,10 +377,7 @@ describe('IDE store project actions', () => {
it('dispatches showEmptyState action right away', done => {
openBranch(store, branch)
.then(() => {
- expect(store.dispatch.calls.allArgs()).toEqual([
- ['setCurrentBranchId', branch.branchId],
- ['showEmptyState', branch],
- ]);
+ expect(store.dispatch.calls.allArgs()).toEqual([['showEmptyState', branch]]);
done();
})
.catch(done.fail);
@@ -281,56 +393,14 @@ describe('IDE store project actions', () => {
openBranch(store, branch)
.then(() => {
expect(store.dispatch.calls.allArgs()).toEqual([
- ['setCurrentBranchId', branch.branchId],
- ['getBranchData', branch],
- ['getMergeRequestsForBranch', branch],
- ['getFiles', branch],
+ ['setCurrentBranchId', branchId],
+ ['loadBranch', { projectId, branchId }],
+ ['loadFile', { basePath: undefined }],
]);
})
.then(done)
.catch(done.fail);
});
-
- it('handles tree entry action, if basePath is given', done => {
- openBranch(store, { ...branch, basePath: 'foo/bar/' })
- .then(() => {
- expect(store.dispatch).toHaveBeenCalledWith(
- 'handleTreeEntryAction',
- store.state.entries['foo/bar'],
- );
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('does not handle tree entry action, if entry is pending', done => {
- openBranch(store, { ...branch, basePath: 'foo/bar-pending' })
- .then(() => {
- expect(store.dispatch).not.toHaveBeenCalledWith(
- 'handleTreeEntryAction',
- jasmine.anything(),
- );
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('creates a new file supplied via URL if the file does not exist yet', done => {
- openBranch(store, { ...branch, basePath: 'not-existent.md' })
- .then(() => {
- expect(store.dispatch).not.toHaveBeenCalledWith(
- 'handleTreeEntryAction',
- jasmine.anything(),
- );
-
- expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', {
- name: 'not-existent.md',
- type: 'blob',
- });
- })
- .then(done)
- .catch(done.fail);
- });
});
describe('non-existent branch', () => {
@@ -342,9 +412,8 @@ describe('IDE store project actions', () => {
openBranch(store, branch)
.then(() => {
expect(store.dispatch.calls.allArgs()).toEqual([
- ['setCurrentBranchId', branch.branchId],
- ['getBranchData', branch],
- ['showBranchNotFoundError', branch.branchId],
+ ['setCurrentBranchId', branchId],
+ ['loadBranch', { projectId, branchId }],
]);
})
.then(done)
diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js
index 8504fb3f42b..7e77b859fdd 100644
--- a/spec/javascripts/ide/stores/actions_spec.js
+++ b/spec/javascripts/ide/stores/actions_spec.js
@@ -13,12 +13,15 @@ import actions, {
createTempEntry,
} from '~/ide/stores/actions';
import axios from '~/lib/utils/axios_utils';
-import store from '~/ide/stores';
+import { createStore } from '~/ide/stores';
import * as types from '~/ide/stores/mutation_types';
import router from '~/ide/ide_router';
import { resetStore, file } from '../helpers';
import testAction from '../../helpers/vuex_action_helper';
import MockAdapter from 'axios-mock-adapter';
+import eventHub from '~/ide/eventhub';
+
+const store = createStore();
describe('Multi-file store actions', () => {
beforeEach(() => {
@@ -451,6 +454,24 @@ describe('Multi-file store actions', () => {
done,
);
});
+
+ it('does not dispatch for parent, if parent does not exist', done => {
+ const f = {
+ ...file(),
+ path: 'test',
+ parentPath: 'testing',
+ };
+ store.state.entries[f.path] = f;
+
+ testAction(
+ updateTempFlagForEntry,
+ { file: f, tempFile: false },
+ store.state,
+ [{ type: 'UPDATE_TEMP_FLAG', payload: { path: f.path, tempFile: false } }],
+ [],
+ done,
+ );
+ });
});
describe('setCurrentBranchId', () => {
@@ -540,82 +561,298 @@ describe('Multi-file store actions', () => {
done,
);
});
- });
- describe('renameEntry', () => {
- it('renames entry', done => {
- store.state.entries.test = {
- tree: [],
+ it('if renamed, reverts the rename before deleting', () => {
+ const testEntry = {
+ path: 'test',
+ name: 'test',
+ prevPath: 'lorem/ipsum',
+ prevName: 'ipsum',
+ prevParentPath: 'lorem',
};
+ store.state.entries = { test: testEntry };
testAction(
- renameEntry,
- { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' },
+ deleteEntry,
+ testEntry.path,
store.state,
+ [],
[
{
- type: types.RENAME_ENTRY,
- payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' },
- },
- {
- type: types.TOGGLE_FILE_CHANGED,
+ type: 'renameEntry',
payload: {
- file: store.state.entries['parent-path/new-name'],
- changed: true,
+ path: testEntry.path,
+ name: testEntry.prevName,
+ parentPath: testEntry.prevParentPath,
},
},
+ {
+ type: 'deleteEntry',
+ payload: testEntry.prevPath,
+ },
],
- [{ type: 'triggerFilesChange' }],
- done,
);
});
+ });
- it('renames all entries in tree', done => {
- store.state.entries.test = {
- type: 'tree',
- tree: [
- {
- path: 'tree-1',
- },
- {
- path: 'tree-2',
+ describe('renameEntry', () => {
+ describe('purging of file model cache', () => {
+ beforeEach(() => {
+ spyOn(eventHub, '$emit');
+ });
+
+ it('does not purge model cache for temporary entries that got renamed', done => {
+ Object.assign(store.state.entries, {
+ test: {
+ ...file('test'),
+ key: 'foo-key',
+ type: 'blob',
+ tempFile: true,
},
- ],
- };
+ });
- testAction(
- renameEntry,
- { path: 'test', name: 'new-name', parentPath: 'parent-path' },
- store.state,
- [
- {
- type: types.RENAME_ENTRY,
- payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' },
+ store
+ .dispatch('renameEntry', {
+ path: 'test',
+ name: 'new',
+ })
+ .then(() => {
+ expect(eventHub.$emit.calls.allArgs()).not.toContain(
+ 'editor.update.model.dispose.foo-bar',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('purges model cache for renamed entry', done => {
+ Object.assign(store.state.entries, {
+ test: {
+ ...file('test'),
+ key: 'foo-key',
+ type: 'blob',
+ tempFile: false,
},
- ],
- [
- {
- type: 'renameEntry',
- payload: {
- path: 'test',
- name: 'new-name',
- entryPath: 'tree-1',
- parentPath: 'parent-path/new-name',
+ });
+
+ store
+ .dispatch('renameEntry', {
+ path: 'test',
+ name: 'new',
+ })
+ .then(() => {
+ expect(eventHub.$emit).toHaveBeenCalled();
+ expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.dispose.foo-key`);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('single entry', () => {
+ let origEntry;
+ let renamedEntry;
+
+ beforeEach(() => {
+ // Need to insert both because `testAction` doesn't actually call the mutation
+ origEntry = file('orig', 'orig', 'blob');
+ renamedEntry = {
+ ...file('renamed', 'renamed', 'blob'),
+ prevKey: origEntry.key,
+ prevName: origEntry.name,
+ prevPath: origEntry.path,
+ };
+
+ Object.assign(store.state.entries, {
+ orig: origEntry,
+ renamed: renamedEntry,
+ });
+ });
+
+ afterEach(() => {
+ resetStore(store);
+ });
+
+ it('by default renames an entry and adds to changed', done => {
+ testAction(
+ renameEntry,
+ { path: 'orig', name: 'renamed' },
+ store.state,
+ [
+ {
+ type: types.RENAME_ENTRY,
+ payload: {
+ path: 'orig',
+ name: 'renamed',
+ parentPath: undefined,
+ },
},
- },
- {
- type: 'renameEntry',
- payload: {
- path: 'test',
- name: 'new-name',
- entryPath: 'tree-2',
- parentPath: 'parent-path/new-name',
+ {
+ type: types.ADD_FILE_TO_CHANGED,
+ payload: 'renamed',
+ },
+ ],
+ [{ type: 'triggerFilesChange' }],
+ done,
+ );
+ });
+
+ it('if not changed, completely unstages entry if renamed to original', done => {
+ testAction(
+ renameEntry,
+ { path: 'renamed', name: 'orig' },
+ store.state,
+ [
+ {
+ type: types.RENAME_ENTRY,
+ payload: {
+ path: 'renamed',
+ name: 'orig',
+ parentPath: undefined,
+ },
+ },
+ {
+ type: types.REMOVE_FILE_FROM_STAGED_AND_CHANGED,
+ payload: origEntry,
+ },
+ ],
+ [{ type: 'triggerFilesChange' }],
+ done,
+ );
+ });
+
+ it('if already in changed, does not add to change', done => {
+ store.state.changedFiles.push(renamedEntry);
+
+ testAction(
+ renameEntry,
+ { path: 'orig', name: 'renamed' },
+ store.state,
+ [jasmine.objectContaining({ type: types.RENAME_ENTRY })],
+ [{ type: 'triggerFilesChange' }],
+ done,
+ );
+ });
+
+ it('routes to the renamed file if the original file has been opened', done => {
+ Object.assign(store.state.entries.orig, {
+ opened: true,
+ url: '/foo-bar.md',
+ });
+
+ store
+ .dispatch('renameEntry', {
+ path: 'orig',
+ name: 'renamed',
+ })
+ .then(() => {
+ expect(router.push.calls.count()).toBe(1);
+ expect(router.push).toHaveBeenCalledWith(`/project/foo-bar.md`);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('folder', () => {
+ let folder;
+ let file1;
+ let file2;
+
+ beforeEach(() => {
+ folder = file('folder', 'folder', 'tree');
+ file1 = file('file-1', 'file-1', 'blob', folder);
+ file2 = file('file-2', 'file-2', 'blob', folder);
+
+ folder.tree = [file1, file2];
+
+ Object.assign(store.state.entries, {
+ [folder.path]: folder,
+ [file1.path]: file1,
+ [file2.path]: file2,
+ });
+ });
+
+ it('updates entries in a folder correctly, when folder is renamed', done => {
+ store
+ .dispatch('renameEntry', {
+ path: 'folder',
+ name: 'new-folder',
+ })
+ .then(() => {
+ const keys = Object.keys(store.state.entries);
+
+ expect(keys.length).toBe(3);
+ expect(keys.indexOf('new-folder')).toBe(0);
+ expect(keys.indexOf('new-folder/file-1')).toBe(1);
+ expect(keys.indexOf('new-folder/file-2')).toBe(2);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('discards renaming of an entry if the root folder is renamed back to a previous name', done => {
+ const rootFolder = file('old-folder', 'old-folder', 'tree');
+ const testEntry = file('test', 'test', 'blob', rootFolder);
+
+ Object.assign(store.state, {
+ entries: {
+ 'old-folder': {
+ ...rootFolder,
+ tree: [testEntry],
},
+ 'old-folder/test': testEntry,
},
- { type: 'triggerFilesChange' },
- ],
- done,
- );
+ });
+
+ store
+ .dispatch('renameEntry', {
+ path: 'old-folder',
+ name: 'new-folder',
+ })
+ .then(() => {
+ const { entries } = store.state;
+
+ expect(Object.keys(entries).length).toBe(2);
+ expect(entries['old-folder']).toBeUndefined();
+ expect(entries['old-folder/test']).toBeUndefined();
+
+ expect(entries['new-folder']).toBeDefined();
+ expect(entries['new-folder/test']).toEqual(
+ jasmine.objectContaining({
+ path: 'new-folder/test',
+ name: 'test',
+ prevPath: 'old-folder/test',
+ prevName: 'test',
+ }),
+ );
+ })
+ .then(() =>
+ store.dispatch('renameEntry', {
+ path: 'new-folder',
+ name: 'old-folder',
+ }),
+ )
+ .then(() => {
+ const { entries } = store.state;
+
+ expect(Object.keys(entries).length).toBe(2);
+ expect(entries['new-folder']).toBeUndefined();
+ expect(entries['new-folder/test']).toBeUndefined();
+
+ expect(entries['old-folder']).toBeDefined();
+ expect(entries['old-folder/test']).toEqual(
+ jasmine.objectContaining({
+ path: 'old-folder/test',
+ name: 'test',
+ prevPath: undefined,
+ prevName: undefined,
+ }),
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
});
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
index 091b454c0d2..95d927065f0 100644
--- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -1,5 +1,5 @@
import rootActions from '~/ide/stores/actions';
-import store from '~/ide/stores';
+import { createStore } from '~/ide/stores';
import service from '~/ide/services';
import router from '~/ide/ide_router';
import eventHub from '~/ide/eventhub';
@@ -11,6 +11,7 @@ import { resetStore, file } from 'spec/ide/helpers';
import testAction from '../../../../helpers/vuex_action_helper';
const TEST_COMMIT_SHA = '123456789';
+const store = createStore();
describe('IDE commit module actions', () => {
beforeEach(() => {
@@ -59,7 +60,9 @@ describe('IDE commit module actions', () => {
});
it('sets shouldCreateMR to true if "Create new MR" option is visible', done => {
- store.state.shouldHideNewMrOption = false;
+ Object.assign(store.state, {
+ shouldHideNewMrOption: false,
+ });
testAction(
actions.updateCommitAction,
@@ -78,7 +81,9 @@ describe('IDE commit module actions', () => {
});
it('sets shouldCreateMR to false if "Create new MR" option is hidden', done => {
- store.state.shouldHideNewMrOption = true;
+ Object.assign(store.state, {
+ shouldHideNewMrOption: true,
+ });
testAction(
actions.updateCommitAction,
@@ -172,24 +177,31 @@ describe('IDE commit module actions', () => {
content: 'file content',
});
- store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
- store.state.projects.abcproject = {
- web_url: 'web_url',
- branches: {
- master: {
- workingReference: '',
- commit: {
- short_id: TEST_COMMIT_SHA,
+ Object.assign(store.state, {
+ currentProjectId: 'abcproject',
+ currentBranchId: 'master',
+ projects: {
+ abcproject: {
+ web_url: 'web_url',
+ branches: {
+ master: {
+ workingReference: '',
+ commit: {
+ short_id: TEST_COMMIT_SHA,
+ },
+ },
},
},
},
- };
- store.state.stagedFiles.push(f, {
- ...file('changedFile2'),
- changed: true,
+ stagedFiles: [
+ f,
+ {
+ ...file('changedFile2'),
+ changed: true,
+ },
+ ],
+ openFiles: store.state.stagedFiles,
});
- store.state.openFiles = store.state.stagedFiles;
store.state.stagedFiles.forEach(stagedFile => {
store.state.entries[stagedFile.path] = stagedFile;
@@ -275,40 +287,40 @@ describe('IDE commit module actions', () => {
document.body.innerHTML += '<div class="flash-container"></div>';
- store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
- store.state.projects.abcproject = {
- web_url: 'webUrl',
- branches: {
- master: {
- workingReference: '1',
- commit: {
- id: TEST_COMMIT_SHA,
- },
- },
- },
- };
-
const f = {
...file('changed'),
type: 'blob',
active: true,
lastCommitSha: TEST_COMMIT_SHA,
};
- store.state.stagedFiles.push(f);
- store.state.changedFiles = [
- {
- ...f,
- },
- ];
- store.state.openFiles = store.state.changedFiles;
- store.state.openFiles.forEach(localF => {
- store.state.entries[localF.path] = localF;
+ Object.assign(store.state, {
+ stagedFiles: [f],
+ changedFiles: [f],
+ openFiles: [f],
+ currentProjectId: 'abcproject',
+ currentBranchId: 'master',
+ projects: {
+ abcproject: {
+ web_url: 'webUrl',
+ branches: {
+ master: {
+ workingReference: '1',
+ commit: {
+ id: TEST_COMMIT_SHA,
+ },
+ },
+ },
+ },
+ },
});
store.state.commit.commitAction = '2';
store.state.commit.commitMessage = 'testing 123';
+
+ store.state.openFiles.forEach(localF => {
+ store.state.entries[localF.path] = localF;
+ });
});
afterEach(() => {
@@ -473,18 +485,16 @@ describe('IDE commit module actions', () => {
});
it('resets changed files before redirecting', done => {
+ visitUrl = visitUrl.and.callFake(() => {
+ expect(store.state.stagedFiles.length).toBe(0);
+ done();
+ });
+
spyOn(eventHub, '$on');
store.state.commit.commitAction = '3';
- store
- .dispatch('commit/commitChanges')
- .then(() => {
- expect(store.state.stagedFiles.length).toBe(0);
-
- done();
- })
- .catch(done.fail);
+ store.dispatch('commit/commitChanges').catch(done.fail);
});
});
});
diff --git a/spec/javascripts/ide/stores/mutations/file_spec.js b/spec/javascripts/ide/stores/mutations/file_spec.js
index 064e66cef64..7c46bf55318 100644
--- a/spec/javascripts/ide/stores/mutations/file_spec.js
+++ b/spec/javascripts/ide/stores/mutations/file_spec.js
@@ -356,16 +356,16 @@ describe('IDE store file mutations', () => {
});
describe('STAGE_CHANGE', () => {
- it('adds file into stagedFiles array', () => {
+ beforeEach(() => {
mutations.STAGE_CHANGE(localState, localFile.path);
+ });
+ it('adds file into stagedFiles array', () => {
expect(localState.stagedFiles.length).toBe(1);
expect(localState.stagedFiles[0]).toEqual(localFile);
});
it('updates stagedFile if it is already staged', () => {
- mutations.STAGE_CHANGE(localState, localFile.path);
-
localFile.raw = 'testing 123';
mutations.STAGE_CHANGE(localState, localFile.path);
@@ -373,19 +373,6 @@ describe('IDE store file mutations', () => {
expect(localState.stagedFiles.length).toBe(1);
expect(localState.stagedFiles[0].raw).toEqual('testing 123');
});
-
- it('adds already-staged file to `replacedFiles`', () => {
- localFile.raw = 'already-staged';
-
- mutations.STAGE_CHANGE(localState, localFile.path);
-
- localFile.raw = 'testing 123';
-
- mutations.STAGE_CHANGE(localState, localFile.path);
-
- expect(localState.replacedFiles.length).toBe(1);
- expect(localState.replacedFiles[0].raw).toEqual('already-staged');
- });
});
describe('UNSTAGE_CHANGE', () => {
diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js
index 2470c99e300..7dd5d323f69 100644
--- a/spec/javascripts/ide/stores/mutations_spec.js
+++ b/spec/javascripts/ide/stores/mutations_spec.js
@@ -79,16 +79,6 @@ describe('Multi-file store mutations', () => {
});
});
- describe('CLEAR_REPLACED_FILES', () => {
- it('clears replacedFiles array', () => {
- localState.replacedFiles.push('a');
-
- mutations.CLEAR_REPLACED_FILES(localState);
-
- expect(localState.replacedFiles.length).toBe(0);
- });
- });
-
describe('UPDATE_VIEWER', () => {
it('sets viewer state', () => {
mutations.UPDATE_VIEWER(localState, 'diff');
@@ -311,8 +301,7 @@ describe('Multi-file store mutations', () => {
describe('UPDATE_FILE_AFTER_COMMIT', () => {
it('updates URLs if prevPath is set', () => {
const f = {
- ...file(),
- path: 'test',
+ ...file('test'),
prevPath: 'testing-123',
rawPath: `${gl.TEST_HOST}/testing-123`,
permalink: `${gl.TEST_HOST}/testing-123`,
@@ -325,19 +314,26 @@ describe('Multi-file store mutations', () => {
mutations.UPDATE_FILE_AFTER_COMMIT(localState, { file: f, lastCommit: { commit: {} } });
- expect(f.rawPath).toBe(`${gl.TEST_HOST}/test`);
- expect(f.permalink).toBe(`${gl.TEST_HOST}/test`);
- expect(f.commitsPath).toBe(`${gl.TEST_HOST}/test`);
- expect(f.blamePath).toBe(`${gl.TEST_HOST}/test`);
- expect(f.replaces).toBe(false);
+ expect(f).toEqual(
+ jasmine.objectContaining({
+ rawPath: `${gl.TEST_HOST}/test`,
+ permalink: `${gl.TEST_HOST}/test`,
+ commitsPath: `${gl.TEST_HOST}/test`,
+ blamePath: `${gl.TEST_HOST}/test`,
+ replaces: false,
+ prevId: undefined,
+ prevPath: undefined,
+ prevName: undefined,
+ prevUrl: undefined,
+ prevKey: undefined,
+ }),
+ );
});
});
describe('OPEN_NEW_ENTRY_MODAL', () => {
it('sets entryModal', () => {
- localState.entries.testPath = {
- ...file(),
- };
+ localState.entries.testPath = file();
mutations.OPEN_NEW_ENTRY_MODAL(localState, { type: 'test', path: 'testPath' });
@@ -356,58 +352,178 @@ describe('Multi-file store mutations', () => {
};
localState.currentProjectId = 'gitlab-ce';
localState.currentBranchId = 'master';
- localState.entries.oldPath = {
- ...file(),
- type: 'blob',
- name: 'oldPath',
- path: 'oldPath',
- url: `${gl.TEST_HOST}/oldPath`,
+ localState.entries = {
+ oldPath: file('oldPath', 'oldPath', 'blob'),
};
});
- it('creates new renamed entry', () => {
+ it('updates existing entry without creating a new one', () => {
+ mutations.RENAME_ENTRY(localState, {
+ path: 'oldPath',
+ name: 'newPath',
+ parentPath: '',
+ });
+
+ expect(localState.entries).toEqual({
+ newPath: jasmine.objectContaining({
+ path: 'newPath',
+ prevPath: 'oldPath',
+ }),
+ });
+ });
+
+ it('correctly handles consecutive renames for the same entry', () => {
mutations.RENAME_ENTRY(localState, {
path: 'oldPath',
name: 'newPath',
+ parentPath: '',
+ });
+
+ mutations.RENAME_ENTRY(localState, {
+ path: 'newPath',
+ name: 'newestPath',
+ parentPath: '',
+ });
+
+ expect(localState.entries).toEqual({
+ newestPath: jasmine.objectContaining({
+ path: 'newestPath',
+ prevPath: 'oldPath',
+ }),
+ });
+ });
+
+ it('correctly handles the same entry within a consecutively renamed folder', () => {
+ const oldPath = file('root-folder/oldPath', 'root-folder/oldPath', 'blob');
+ localState.entries = {
+ 'root-folder': {
+ ...file('root-folder', 'root-folder', 'tree'),
+ tree: [oldPath],
+ },
+ 'root-folder/oldPath': oldPath,
+ };
+ Object.assign(localState.entries['root-folder/oldPath'], {
+ parentPath: 'root-folder',
+ url: 'root-folder/oldPath-blob-root-folder/oldPath',
+ });
+
+ mutations.RENAME_ENTRY(localState, {
+ path: 'root-folder/oldPath',
+ name: 'renamed-folder/oldPath',
entryPath: null,
parentPath: '',
});
+ mutations.RENAME_ENTRY(localState, {
+ path: 'renamed-folder/oldPath',
+ name: 'simply-renamed/oldPath',
+ entryPath: null,
+ parentPath: '',
+ });
+
+ expect(localState.entries).toEqual({
+ 'root-folder': jasmine.objectContaining({
+ path: 'root-folder',
+ }),
+ 'simply-renamed/oldPath': jasmine.objectContaining({
+ path: 'simply-renamed/oldPath',
+ prevPath: 'root-folder/oldPath',
+ }),
+ });
+ });
+
+ it('renames entry, preserving old parameters', () => {
+ Object.assign(localState.entries.oldPath, {
+ url: `project/-/oldPath`,
+ });
+ const oldPathData = localState.entries.oldPath;
+
+ mutations.RENAME_ENTRY(localState, {
+ path: 'oldPath',
+ name: 'newPath',
+ parentPath: '',
+ });
+
expect(localState.entries.newPath).toEqual({
- ...localState.entries.oldPath,
+ ...oldPathData,
id: 'newPath',
- name: 'newPath',
- key: 'newPath-blob-oldPath',
path: 'newPath',
- tempFile: true,
+ name: 'newPath',
+ url: `project/-/newPath`,
+ key: jasmine.stringMatching('newPath'),
+
+ prevId: 'oldPath',
+ prevName: 'oldPath',
prevPath: 'oldPath',
- tree: [],
- parentPath: '',
- url: `${gl.TEST_HOST}/newPath`,
- moved: jasmine.anything(),
- movedPath: jasmine.anything(),
- opened: false,
+ prevUrl: `project/-/oldPath`,
+ prevKey: oldPathData.key,
+ prevParentPath: oldPathData.parentPath,
});
});
- it('adds new entry to changedFiles', () => {
- mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' });
+ it('does not store previous attributes on temp files', () => {
+ Object.assign(localState.entries.oldPath, {
+ tempFile: true,
+ });
+ mutations.RENAME_ENTRY(localState, {
+ path: 'oldPath',
+ name: 'newPath',
+ entryPath: null,
+ parentPath: '',
+ });
- expect(localState.changedFiles.length).toBe(1);
- expect(localState.changedFiles[0].path).toBe('newPath');
- });
+ expect(localState.entries.newPath).not.toEqual(
+ jasmine.objectContaining({
+ prevId: jasmine.anything(),
+ prevName: jasmine.anything(),
+ prevPath: jasmine.anything(),
+ prevUrl: jasmine.anything(),
+ prevKey: jasmine.anything(),
+ prevParentPath: jasmine.anything(),
+ }),
+ );
+ });
+
+ it('properly handles files with spaces in name', () => {
+ const path = 'my fancy path';
+ const newPath = 'new path';
+ const oldEntry = {
+ ...file(path, path, 'blob'),
+ url: `project/-/${encodeURI(path)}`,
+ };
- it('sets oldEntry as moved', () => {
- mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' });
+ localState.entries[path] = oldEntry;
- expect(localState.entries.oldPath.moved).toBe(true);
+ mutations.RENAME_ENTRY(localState, {
+ path,
+ name: newPath,
+ entryPath: null,
+ parentPath: '',
+ });
+
+ expect(localState.entries[newPath]).toEqual({
+ ...oldEntry,
+ id: newPath,
+ path: newPath,
+ name: newPath,
+ url: `project/-/new%20path`,
+ key: jasmine.stringMatching(newPath),
+
+ prevId: path,
+ prevName: path,
+ prevPath: path,
+ prevUrl: `project/-/my%20fancy%20path`,
+ prevKey: oldEntry.key,
+ prevParentPath: oldEntry.parentPath,
+ });
});
- it('adds to parents tree', () => {
- localState.entries.oldPath.parentPath = 'parentPath';
- localState.entries.parentPath = {
- ...file(),
+ it('adds to parent tree', () => {
+ const parentEntry = {
+ ...file('parentPath', 'parentPath', 'tree'),
+ tree: [localState.entries.oldPath],
};
+ localState.entries.parentPath = parentEntry;
mutations.RENAME_ENTRY(localState, {
path: 'oldPath',
@@ -416,7 +532,180 @@ describe('Multi-file store mutations', () => {
parentPath: 'parentPath',
});
- expect(localState.entries.parentPath.tree.length).toBe(1);
+ expect(parentEntry.tree.length).toBe(1);
+ expect(parentEntry.tree[0].name).toBe('newPath');
+ });
+
+ it('sorts tree after renaming an entry', () => {
+ const alpha = file('alpha', 'alpha', 'blob');
+ const beta = file('beta', 'beta', 'blob');
+ const gamma = file('gamma', 'gamma', 'blob');
+ localState.entries = { alpha, beta, gamma };
+
+ localState.trees['gitlab-ce/master'].tree = [alpha, beta, gamma];
+
+ mutations.RENAME_ENTRY(localState, {
+ path: 'alpha',
+ name: 'theta',
+ entryPath: null,
+ parentPath: '',
+ });
+
+ expect(localState.trees['gitlab-ce/master'].tree).toEqual([
+ jasmine.objectContaining({ name: 'beta' }),
+ jasmine.objectContaining({ name: 'gamma' }),
+ jasmine.objectContaining({
+ path: 'theta',
+ name: 'theta',
+ }),
+ ]);
+ });
+
+ it('updates openFiles with the renamed one if the original one is open', () => {
+ Object.assign(localState.entries.oldPath, {
+ opened: true,
+ type: 'blob',
+ });
+ Object.assign(localState, {
+ openFiles: [localState.entries.oldPath],
+ });
+
+ mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' });
+
+ expect(localState.openFiles.length).toBe(1);
+ expect(localState.openFiles[0].path).toBe('newPath');
+ });
+
+ it('does not add renamed entry to changedFiles', () => {
+ mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' });
+
+ expect(localState.changedFiles.length).toBe(0);
+ });
+
+ it('updates existing changedFiles entry with the renamed one', () => {
+ const origFile = {
+ ...file('oldPath', 'oldPath', 'blob'),
+ content: 'Foo',
+ };
+
+ Object.assign(localState, {
+ changedFiles: [origFile],
+ });
+ Object.assign(localState.entries, {
+ oldPath: origFile,
+ });
+
+ mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' });
+
+ expect(localState.changedFiles).toEqual([
+ jasmine.objectContaining({
+ path: 'newPath',
+ content: 'Foo',
+ }),
+ ]);
+ });
+
+ it('correctly saves original values if an entry is renamed multiple times', () => {
+ const original = { ...localState.entries.oldPath };
+ const paramsToCheck = ['prevId', 'prevPath', 'prevName', 'prevUrl'];
+ const expectedObj = paramsToCheck.reduce(
+ (o, param) => ({ ...o, [param]: original[param.replace('prev', '').toLowerCase()] }),
+ {},
+ );
+
+ mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' });
+
+ expect(localState.entries.newPath).toEqual(jasmine.objectContaining(expectedObj));
+
+ mutations.RENAME_ENTRY(localState, { path: 'newPath', name: 'newer' });
+
+ expect(localState.entries.newer).toEqual(jasmine.objectContaining(expectedObj));
+ });
+
+ describe('renaming back to original', () => {
+ beforeEach(() => {
+ const renamedEntry = {
+ ...file('renamed', 'renamed', 'blob'),
+ prevId: 'lorem/orig',
+ prevPath: 'lorem/orig',
+ prevName: 'orig',
+ prevUrl: 'project/-/loren/orig',
+ prevKey: 'lorem/orig',
+ prevParentPath: 'lorem',
+ };
+
+ localState.entries = {
+ renamed: renamedEntry,
+ };
+
+ mutations.RENAME_ENTRY(localState, { path: 'renamed', name: 'orig', parentPath: 'lorem' });
+ });
+
+ it('renames entry and clears prev properties', () => {
+ expect(localState.entries).toEqual({
+ 'lorem/orig': jasmine.objectContaining({
+ id: 'lorem/orig',
+ path: 'lorem/orig',
+ name: 'orig',
+ prevId: undefined,
+ prevPath: undefined,
+ prevName: undefined,
+ prevUrl: undefined,
+ prevKey: undefined,
+ prevParentPath: undefined,
+ }),
+ });
+ });
+ });
+
+ describe('key updates', () => {
+ beforeEach(() => {
+ const rootFolder = file('rootFolder', 'rootFolder', 'tree');
+ localState.entries = {
+ rootFolder,
+ oldPath: file('oldPath', 'oldPath', 'blob'),
+ 'oldPath.txt': file('oldPath.txt', 'oldPath.txt', 'blob'),
+ 'rootFolder/oldPath.md': file('oldPath.md', 'oldPath.md', 'blob', rootFolder),
+ };
+ });
+
+ it('sets properly constucted key while preserving the original one', () => {
+ const key = 'oldPath.txt-blob-oldPath.txt';
+ localState.entries['oldPath.txt'].key = key;
+ mutations.RENAME_ENTRY(localState, { path: 'oldPath.txt', name: 'newPath.md' });
+
+ expect(localState.entries['newPath.md'].key).toBe('newPath.md-blob-newPath.md');
+ expect(localState.entries['newPath.md'].prevKey).toBe(key);
+ });
+
+ it('correctly updates key for an entry without an extension', () => {
+ localState.entries.oldPath.key = 'oldPath-blob-oldPath';
+ mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath.md' });
+
+ expect(localState.entries['newPath.md'].key).toBe('newPath.md-blob-newPath.md');
+ });
+
+ it('correctly updates key when new name does not have an extension', () => {
+ localState.entries['oldPath.txt'].key = 'oldPath.txt-blob-oldPath.txt';
+ mutations.RENAME_ENTRY(localState, { path: 'oldPath.txt', name: 'newPath' });
+
+ expect(localState.entries.newPath.key).toBe('newPath-blob-newPath');
+ });
+
+ it('correctly updates key when renaming an entry in a folder', () => {
+ localState.entries['rootFolder/oldPath.md'].key =
+ 'rootFolder/oldPath.md-blob-rootFolder/oldPath.md';
+ mutations.RENAME_ENTRY(localState, {
+ path: 'rootFolder/oldPath.md',
+ name: 'newPath.md',
+ entryPath: null,
+ parentPath: 'rootFolder',
+ });
+
+ expect(localState.entries['rootFolder/newPath.md'].key).toBe(
+ 'rootFolder/newPath.md-blob-rootFolder/newPath.md',
+ );
+ });
});
});
});
diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js
index 0fc9519a6bf..a477d4fc200 100644
--- a/spec/javascripts/ide/stores/utils_spec.js
+++ b/spec/javascripts/ide/stores/utils_spec.js
@@ -237,31 +237,6 @@ describe('Multi-file store utils', () => {
});
describe('getCommitFiles', () => {
- it('returns list of files excluding moved files', () => {
- const files = [
- {
- path: 'a',
- type: 'blob',
- deleted: true,
- },
- {
- path: 'c',
- type: 'blob',
- moved: true,
- },
- ];
-
- const flattendFiles = utils.getCommitFiles(files);
-
- expect(flattendFiles).toEqual([
- {
- path: 'a',
- type: 'blob',
- deleted: true,
- },
- ]);
- });
-
it('filters out folders from the list', () => {
const files = [
{
@@ -422,4 +397,204 @@ describe('Multi-file store utils', () => {
expect(res[1].tree[0].opened).toEqual(true);
});
});
+
+ describe('escapeFileUrl', () => {
+ it('encodes URL excluding the slashes', () => {
+ expect(utils.escapeFileUrl('/foo-bar/file.md')).toBe('/foo-bar/file.md');
+ expect(utils.escapeFileUrl('foo bar/file.md')).toBe('foo%20bar/file.md');
+ expect(utils.escapeFileUrl('foo/bar/file.md')).toBe('foo/bar/file.md');
+ });
+ });
+
+ describe('swapInStateArray', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = [];
+ });
+
+ it('swaps existing entry with a new one', () => {
+ const file1 = {
+ ...file('old'),
+ key: 'foo',
+ };
+ const file2 = file('new');
+ const arr = [file1];
+
+ Object.assign(localState, {
+ dummyArray: arr,
+ entries: {
+ new: file2,
+ },
+ });
+
+ utils.swapInStateArray(localState, 'dummyArray', 'foo', 'new');
+
+ expect(localState.dummyArray.length).toBe(1);
+ expect(localState.dummyArray[0]).toBe(file2);
+ });
+
+ it('does not add an item if it does not exist yet in array', () => {
+ const file1 = file('file');
+ Object.assign(localState, {
+ dummyArray: [],
+ entries: {
+ file: file1,
+ },
+ });
+
+ utils.swapInStateArray(localState, 'dummyArray', 'foo', 'file');
+
+ expect(localState.dummyArray.length).toBe(0);
+ });
+ });
+
+ describe('swapInParentTreeWithSorting', () => {
+ let localState;
+ let branchInfo;
+ const currentProjectId = '123-foo';
+ const currentBranchId = 'master';
+
+ beforeEach(() => {
+ localState = {
+ currentBranchId,
+ currentProjectId,
+ trees: {
+ [`${currentProjectId}/${currentBranchId}`]: {
+ tree: [],
+ },
+ },
+ entries: {
+ oldPath: file('oldPath', 'oldPath', 'blob'),
+ newPath: file('newPath', 'newPath', 'blob'),
+ parentPath: file('parentPath', 'parentPath', 'tree'),
+ },
+ };
+ branchInfo = localState.trees[`${currentProjectId}/${currentBranchId}`];
+ });
+
+ it('does not change tree if newPath is not supplied', () => {
+ branchInfo.tree = [localState.entries.oldPath];
+
+ utils.swapInParentTreeWithSorting(localState, 'oldPath', undefined, undefined);
+
+ expect(branchInfo.tree).toEqual([localState.entries.oldPath]);
+ });
+
+ describe('oldPath to replace is not defined: simple addition to tree', () => {
+ it('adds to tree on the state if there is no parent for the entry', () => {
+ expect(branchInfo.tree.length).toBe(0);
+
+ utils.swapInParentTreeWithSorting(localState, undefined, 'oldPath', undefined);
+
+ expect(branchInfo.tree.length).toBe(1);
+ expect(branchInfo.tree[0].name).toBe('oldPath');
+
+ utils.swapInParentTreeWithSorting(localState, undefined, 'newPath', undefined);
+
+ expect(branchInfo.tree.length).toBe(2);
+ expect(branchInfo.tree).toEqual([
+ jasmine.objectContaining({ name: 'newPath' }),
+ jasmine.objectContaining({ name: 'oldPath' }),
+ ]);
+ });
+
+ it('adds to parent tree if it is supplied', () => {
+ utils.swapInParentTreeWithSorting(localState, undefined, 'newPath', 'parentPath');
+
+ expect(localState.entries.parentPath.tree.length).toBe(1);
+ expect(localState.entries.parentPath.tree).toEqual([
+ jasmine.objectContaining({ name: 'newPath' }),
+ ]);
+
+ localState.entries.parentPath.tree = [localState.entries.oldPath];
+
+ utils.swapInParentTreeWithSorting(localState, undefined, 'newPath', 'parentPath');
+
+ expect(localState.entries.parentPath.tree.length).toBe(2);
+ expect(localState.entries.parentPath.tree).toEqual([
+ jasmine.objectContaining({ name: 'newPath' }),
+ jasmine.objectContaining({ name: 'oldPath' }),
+ ]);
+ });
+ });
+
+ describe('swapping of the items', () => {
+ it('swaps entries if both paths are supplied', () => {
+ branchInfo.tree = [localState.entries.oldPath];
+
+ utils.swapInParentTreeWithSorting(localState, localState.entries.oldPath.key, 'newPath');
+
+ expect(branchInfo.tree).toEqual([jasmine.objectContaining({ name: 'newPath' })]);
+
+ utils.swapInParentTreeWithSorting(localState, localState.entries.newPath.key, 'oldPath');
+
+ expect(branchInfo.tree).toEqual([jasmine.objectContaining({ name: 'oldPath' })]);
+ });
+
+ it('sorts tree after swapping the entries', () => {
+ const alpha = file('alpha', 'alpha', 'blob');
+ const beta = file('beta', 'beta', 'blob');
+ const gamma = file('gamma', 'gamma', 'blob');
+ const theta = file('theta', 'theta', 'blob');
+ localState.entries = { alpha, beta, gamma, theta };
+
+ branchInfo.tree = [alpha, beta, gamma];
+
+ utils.swapInParentTreeWithSorting(localState, alpha.key, 'theta');
+
+ expect(branchInfo.tree).toEqual([
+ jasmine.objectContaining({ name: 'beta' }),
+ jasmine.objectContaining({ name: 'gamma' }),
+ jasmine.objectContaining({ name: 'theta' }),
+ ]);
+
+ utils.swapInParentTreeWithSorting(localState, gamma.key, 'alpha');
+
+ expect(branchInfo.tree).toEqual([
+ jasmine.objectContaining({ name: 'alpha' }),
+ jasmine.objectContaining({ name: 'beta' }),
+ jasmine.objectContaining({ name: 'theta' }),
+ ]);
+
+ utils.swapInParentTreeWithSorting(localState, beta.key, 'gamma');
+
+ expect(branchInfo.tree).toEqual([
+ jasmine.objectContaining({ name: 'alpha' }),
+ jasmine.objectContaining({ name: 'gamma' }),
+ jasmine.objectContaining({ name: 'theta' }),
+ ]);
+ });
+ });
+ });
+
+ describe('cleanTrailingSlash', () => {
+ [
+ { input: '', output: '' },
+ { input: 'abc', output: 'abc' },
+ { input: 'abc/', output: 'abc' },
+ { input: 'abc/def', output: 'abc/def' },
+ { input: 'abc/def/', output: 'abc/def' },
+ ].forEach(({ input, output }) => {
+ it(`cleans trailing slash from string "${input}"`, () => {
+ expect(utils.cleanTrailingSlash(input)).toEqual(output);
+ });
+ });
+ });
+
+ describe('pathsAreEqual', () => {
+ [
+ { args: ['abc', 'abc'], output: true },
+ { args: ['abc', 'def'], output: false },
+ { args: ['abc/', 'abc'], output: true },
+ { args: ['abc/abc', 'abc'], output: false },
+ { args: ['/', ''], output: true },
+ { args: ['', '/'], output: true },
+ { args: [false, '/'], output: true },
+ ].forEach(({ args, output }) => {
+ it(`cleans and tests equality (${JSON.stringify(args)})`, () => {
+ expect(utils.pathsAreEqual(...args)).toEqual(output);
+ });
+ });
+ });
});
diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js
index 069e2cb07b5..82d1f815ca8 100644
--- a/spec/javascripts/integrations/integration_settings_form_spec.js
+++ b/spec/javascripts/integrations/integration_settings_form_spec.js
@@ -126,6 +126,7 @@ describe('IntegrationSettingsForm', () => {
spyOn(axios, 'put').and.callThrough();
integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
+ // eslint-disable-next-line no-jquery/no-serialize
formData = integrationSettingsForm.$form.serialize();
});
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 2770743937e..9fce040fd8c 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -1,3 +1,5 @@
+/* eslint-disable no-unused-vars */
+import GLDropdown from '~/gl_dropdown';
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
@@ -21,6 +23,14 @@ describe('Issuable output', () => {
beforeEach(done => {
setFixtures(`
<div>
+ <div class="detail-page-description content-block">
+ <details open>
+ <summary>One</summary>
+ </details>
+ <details>
+ <summary>Two</summary>
+ </details>
+ </div>
<div class="flash-container"></div>
<span id="task_status"></span>
</div>
@@ -52,6 +62,7 @@ describe('Issuable output', () => {
markdownDocsPath: '/',
projectNamespace: '/',
projectPath: '/',
+ issuableTemplateNamesPath: '/issuable-templates-path',
},
}).$mount();
@@ -129,11 +140,11 @@ describe('Issuable output', () => {
});
it('does not update formState if form is already open', done => {
- vm.openForm();
+ vm.updateAndShowForm();
vm.state.titleText = 'testing 123';
- vm.openForm();
+ vm.updateAndShowForm();
Vue.nextTick(() => {
expect(vm.store.formState.title).not.toBe('testing 123');
@@ -284,7 +295,7 @@ describe('Issuable output', () => {
});
});
- it('shows error mesage from backend if exists', done => {
+ it('shows error message from backend if exists', done => {
const msg = 'Custom error message from backend';
spyOn(vm.service, 'updateIssuable').and.callFake(
// eslint-disable-next-line prefer-promise-reject-errors
@@ -405,20 +416,20 @@ describe('Issuable output', () => {
});
});
- describe('open form', () => {
+ describe('updateAndShowForm', () => {
it('shows locked warning if form is open & data is different', done => {
vm.$nextTick()
.then(() => {
- vm.openForm();
+ vm.updateAndShowForm();
vm.poll.makeRequest();
+
+ return new Promise(resolve => {
+ vm.$watch('formState.lockedWarningVisible', value => {
+ if (value) resolve();
+ });
+ });
})
- // Wait for the request
- .then(vm.$nextTick)
- // Wait for the successCallback to update the store state
- .then(vm.$nextTick)
- // Wait for the new state to flow to the Vue components
- .then(vm.$nextTick)
.then(() => {
expect(vm.formState.lockedWarningVisible).toEqual(true);
expect(vm.formState.lock_version).toEqual(1);
@@ -429,6 +440,41 @@ describe('Issuable output', () => {
});
});
+ describe('requestTemplatesAndShowForm', () => {
+ beforeEach(() => {
+ spyOn(vm, 'updateAndShowForm');
+ });
+
+ it('shows the form if template names request is successful', done => {
+ const mockData = [{ name: 'Bug' }];
+ mock.onGet('/issuable-templates-path').reply(() => Promise.resolve([200, mockData]));
+
+ vm.requestTemplatesAndShowForm()
+ .then(() => {
+ expect(vm.updateAndShowForm).toHaveBeenCalledWith(mockData);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('shows the form if template names request failed', done => {
+ mock
+ .onGet('/issuable-templates-path')
+ .reply(() => Promise.reject(new Error('something went wrong')));
+
+ vm.requestTemplatesAndShowForm()
+ .then(() => {
+ expect(document.querySelector('.flash-container .flash-text').textContent).toContain(
+ 'Error updating issue',
+ );
+
+ expect(vm.updateAndShowForm).toHaveBeenCalledWith();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('show inline edit button', () => {
it('should not render by default', () => {
expect(vm.$el.querySelector('.title-container .note-action-button')).toBeDefined();
diff --git a/spec/javascripts/jobs/components/environments_block_spec.js b/spec/javascripts/jobs/components/environments_block_spec.js
index 4bbc5f5a348..64a59d659a7 100644
--- a/spec/javascripts/jobs/components/environments_block_spec.js
+++ b/spec/javascripts/jobs/components/environments_block_spec.js
@@ -2,6 +2,9 @@ import Vue from 'vue';
import component from '~/jobs/components/environments_block.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
+const TEST_CLUSTER_NAME = 'test_cluster';
+const TEST_CLUSTER_PATH = 'path/to/test_cluster';
+
describe('Environments block', () => {
const Component = Vue.extend(component);
let vm;
@@ -20,22 +23,53 @@ describe('Environments block', () => {
const lastDeployment = { iid: 'deployment', deployable: { build_path: 'bar' } };
+ const createEnvironmentWithLastDeployment = () => ({
+ ...environment,
+ last_deployment: { ...lastDeployment },
+ });
+
+ const createEnvironmentWithCluster = () => ({
+ ...environment,
+ last_deployment: {
+ ...lastDeployment,
+ cluster: { name: TEST_CLUSTER_NAME, path: TEST_CLUSTER_PATH },
+ },
+ });
+
+ const createComponent = (deploymentStatus = {}) => {
+ vm = mountComponent(Component, {
+ deploymentStatus,
+ iconStatus: status,
+ });
+ };
+
+ const findText = () => vm.$el.textContent.trim();
+ const findJobDeploymentLink = () => vm.$el.querySelector('.js-job-deployment-link');
+ const findEnvironmentLink = () => vm.$el.querySelector('.js-environment-link');
+ const findClusterLink = () => vm.$el.querySelector('.js-job-cluster-link');
+
afterEach(() => {
vm.$destroy();
});
describe('with last deployment', () => {
it('renders info for most recent deployment', () => {
- vm = mountComponent(Component, {
- deploymentStatus: {
- status: 'last',
- environment,
- },
- iconStatus: status,
+ createComponent({
+ status: 'last',
+ environment,
});
- expect(vm.$el.textContent.trim()).toEqual(
- 'This job is the most recent deployment to environment.',
+ expect(findText()).toEqual('This job is deployed to environment.');
+ });
+
+ it('renders info with cluster', () => {
+ createComponent({
+ status: 'last',
+ environment: createEnvironmentWithCluster(),
+ });
+
+ expect(findText()).toEqual(
+ `This job is deployed to environment using cluster ${TEST_CLUSTER_NAME}.`,
);
});
});
@@ -43,133 +77,106 @@ describe('Environments block', () => {
describe('with out of date deployment', () => {
describe('with last deployment', () => {
it('renders info for out date and most recent', () => {
- vm = mountComponent(Component, {
- deploymentStatus: {
- status: 'out_of_date',
- environment: Object.assign({}, environment, {
- last_deployment: lastDeployment,
- }),
- },
- iconStatus: status,
+ createComponent({
+ status: 'out_of_date',
+ environment: createEnvironmentWithLastDeployment(),
});
- expect(vm.$el.textContent.trim()).toEqual(
- 'This job is an out-of-date deployment to environment. View the most recent deployment #deployment.',
+ expect(findText()).toEqual(
+ 'This job is an out-of-date deployment to environment. View the most recent deployment.',
);
- expect(vm.$el.querySelector('.js-job-deployment-link').getAttribute('href')).toEqual('bar');
+ expect(findJobDeploymentLink().getAttribute('href')).toEqual('bar');
+ });
+
+ it('renders info with cluster', () => {
+ createComponent({
+ status: 'out_of_date',
+ environment: createEnvironmentWithCluster(),
+ });
+
+ expect(findText()).toEqual(
+ `This job is an out-of-date deployment to environment using cluster ${TEST_CLUSTER_NAME}. View the most recent deployment.`,
+ );
});
});
describe('without last deployment', () => {
it('renders info about out of date deployment', () => {
- vm = mountComponent(Component, {
- deploymentStatus: {
- status: 'out_of_date',
- environment,
- },
- iconStatus: status,
+ createComponent({
+ status: 'out_of_date',
+ environment,
});
- expect(vm.$el.textContent.trim()).toEqual(
- 'This job is an out-of-date deployment to environment.',
- );
+ expect(findText()).toEqual('This job is an out-of-date deployment to environment.');
});
});
});
describe('with failed deployment', () => {
it('renders info about failed deployment', () => {
- vm = mountComponent(Component, {
- deploymentStatus: {
- status: 'failed',
- environment,
- },
- iconStatus: status,
+ createComponent({
+ status: 'failed',
+ environment,
});
- expect(vm.$el.textContent.trim()).toEqual(
- 'The deployment of this job to environment did not succeed.',
- );
+ expect(findText()).toEqual('The deployment of this job to environment did not succeed.');
});
});
describe('creating deployment', () => {
describe('with last deployment', () => {
it('renders info about creating deployment and overriding latest deployment', () => {
- vm = mountComponent(Component, {
- deploymentStatus: {
- status: 'creating',
- environment: Object.assign({}, environment, {
- last_deployment: lastDeployment,
- }),
- },
- iconStatus: status,
+ createComponent({
+ status: 'creating',
+ environment: createEnvironmentWithLastDeployment(),
});
- expect(vm.$el.textContent.trim()).toEqual(
- 'This job is creating a deployment to environment and will overwrite the latest deployment.',
+ expect(findText()).toEqual(
+ 'This job is creating a deployment to environment. This will overwrite the latest deployment.',
);
- expect(vm.$el.querySelector('.js-job-deployment-link').getAttribute('href')).toEqual('bar');
+ expect(findJobDeploymentLink().getAttribute('href')).toEqual('bar');
+ expect(findEnvironmentLink().getAttribute('href')).toEqual(environment.environment_path);
+ expect(findClusterLink()).toBeNull();
});
});
describe('without last deployment', () => {
it('renders info about failed deployment', () => {
- vm = mountComponent(Component, {
- deploymentStatus: {
- status: 'creating',
- environment,
- },
- iconStatus: status,
+ createComponent({
+ status: 'creating',
+ environment,
});
- expect(vm.$el.textContent.trim()).toEqual(
- 'This job is creating a deployment to environment.',
- );
+ expect(findText()).toEqual('This job is creating a deployment to environment.');
});
});
describe('without environment', () => {
it('does not render environment link', () => {
- vm = mountComponent(Component, {
- deploymentStatus: {
- status: 'creating',
- environment: null,
- },
- iconStatus: status,
+ createComponent({
+ status: 'creating',
+ environment: null,
});
- expect(vm.$el.querySelector('.js-environment-link')).toBeNull();
+ expect(findEnvironmentLink()).toBeNull();
});
});
});
describe('with a cluster', () => {
it('renders the cluster link', () => {
- const cluster = {
- name: 'the-cluster',
- path: '/the-cluster-path',
- };
- vm = mountComponent(Component, {
- deploymentStatus: {
- status: 'last',
- environment: Object.assign({}, environment, {
- last_deployment: {
- ...lastDeployment,
- cluster,
- },
- }),
- },
- iconStatus: status,
+ createComponent({
+ status: 'last',
+ environment: createEnvironmentWithCluster(),
});
- expect(vm.$el.textContent.trim()).toContain('Cluster the-cluster was used.');
-
- expect(vm.$el.querySelector('.js-job-cluster-link').getAttribute('href')).toEqual(
- '/the-cluster-path',
+ expect(findText()).toEqual(
+ `This job is deployed to environment using cluster ${TEST_CLUSTER_NAME}.`,
);
+
+ expect(findClusterLink().getAttribute('href')).toEqual(TEST_CLUSTER_PATH);
});
describe('when the cluster is missing the path', () => {
@@ -177,39 +184,20 @@ describe('Environments block', () => {
const cluster = {
name: 'the-cluster',
};
- vm = mountComponent(Component, {
- deploymentStatus: {
- status: 'last',
- environment: Object.assign({}, environment, {
- last_deployment: {
- ...lastDeployment,
- cluster,
- },
- }),
- },
- iconStatus: status,
- });
-
- expect(vm.$el.textContent.trim()).toContain('Cluster the-cluster was used.');
-
- expect(vm.$el.querySelector('.js-job-cluster-link')).toBeNull();
- });
- });
- });
-
- describe('without a cluster', () => {
- it('does not render a cluster link', () => {
- vm = mountComponent(Component, {
- deploymentStatus: {
+ createComponent({
status: 'last',
environment: Object.assign({}, environment, {
- last_deployment: lastDeployment,
+ last_deployment: {
+ ...lastDeployment,
+ cluster,
+ },
}),
- },
- iconStatus: status,
- });
+ });
+
+ expect(findText()).toContain('using cluster the-cluster.');
- expect(vm.$el.querySelector('.js-job-cluster-link')).toBeNull();
+ expect(findClusterLink()).toBeNull();
+ });
});
});
});
diff --git a/spec/javascripts/jobs/components/job_log_spec.js b/spec/javascripts/jobs/components/job_log_spec.js
index 24bb6b9a48b..dd58f234394 100644
--- a/spec/javascripts/jobs/components/job_log_spec.js
+++ b/spec/javascripts/jobs/components/job_log_spec.js
@@ -3,7 +3,6 @@ import component from '~/jobs/components/job_log.vue';
import createStore from '~/jobs/store';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../store/helpers';
-import { logWithCollapsibleSections } from '../mock_data';
describe('Job Log', () => {
const Component = Vue.extend(component);
@@ -63,60 +62,4 @@ describe('Job Log', () => {
expect(vm.$el.querySelector('.js-log-animation')).toBeNull();
});
});
-
- describe('Collapsible sections', () => {
- beforeEach(() => {
- vm = mountComponentWithStore(Component, {
- props: {
- trace: logWithCollapsibleSections.html,
- isComplete: true,
- },
- store,
- });
- });
-
- it('renders open arrow', () => {
- expect(vm.$el.querySelector('.fa-caret-down')).not.toBeNull();
- });
-
- it('toggles hidden class to the sibilings rows when arrow is clicked', done => {
- vm.$nextTick()
- .then(() => {
- const { section } = vm.$el.querySelector('.js-section-start').dataset;
- vm.$el.querySelector('.js-section-start').click();
-
- vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
- expect(el.classList.contains('hidden')).toEqual(true);
- });
-
- vm.$el.querySelector('.js-section-start').click();
-
- vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
- expect(el.classList.contains('hidden')).toEqual(false);
- });
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('toggles hidden class to the sibilings rows when header section is clicked', done => {
- vm.$nextTick()
- .then(() => {
- const { section } = vm.$el.querySelector('.js-section-header').dataset;
- vm.$el.querySelector('.js-section-header').click();
-
- vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
- expect(el.classList.contains('hidden')).toEqual(true);
- });
-
- vm.$el.querySelector('.js-section-header').click();
-
- vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
- expect(el.classList.contains('hidden')).toEqual(false);
- });
- })
- .then(done)
- .catch(done.fail);
- });
- });
});
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js
index ccf439aac74..5ae5643aefc 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js
+++ b/spec/javascripts/labels_issue_sidebar_spec.js
@@ -5,6 +5,7 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import IssuableContext from '~/issuable_context';
import LabelsSelect from '~/labels_select';
+import _ from 'underscore';
import '~/gl_dropdown';
import 'select2';
@@ -15,6 +16,35 @@ import '~/users_select';
let saveLabelCount = 0;
let mock;
+function testLabelClicks(labelOrder, done) {
+ $('.edit-link')
+ .get(0)
+ .click();
+
+ setTimeout(() => {
+ const labelsInDropdown = $('.dropdown-content a');
+
+ expect(labelsInDropdown.length).toBe(10);
+
+ const arrayOfLabels = labelsInDropdown.get();
+ const randomArrayOfLabels = _.shuffle(arrayOfLabels);
+ randomArrayOfLabels.forEach((label, i) => {
+ if (i < saveLabelCount) {
+ $(label).click();
+ }
+ });
+
+ $('.edit-link')
+ .get(0)
+ .click();
+
+ setTimeout(() => {
+ expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe(labelOrder);
+ done();
+ }, 0);
+ }, 0);
+}
+
describe('Issue dropdown sidebar', () => {
preloadFixtures('static/issue_sidebar_label.html');
@@ -29,7 +59,7 @@ describe('Issue dropdown sidebar', () => {
mock.onGet('/root/test/labels.json').reply(() => {
const labels = Array(10)
.fill()
- .map((_, i) => ({
+ .map((_val, i) => ({
id: i,
title: `test ${i}`,
color: '#5CB85C',
@@ -41,7 +71,7 @@ describe('Issue dropdown sidebar', () => {
mock.onPut('/root/test/issues/2.json').reply(() => {
const labels = Array(saveLabelCount)
.fill()
- .map((_, i) => ({
+ .map((_val, i) => ({
id: i,
title: `test ${i}`,
color: '#5CB85C',
@@ -57,61 +87,11 @@ describe('Issue dropdown sidebar', () => {
it('changes collapsed tooltip when changing labels when less than 5', done => {
saveLabelCount = 5;
- $('.edit-link')
- .get(0)
- .click();
-
- setTimeout(() => {
- expect($('.dropdown-content a').length).toBe(10);
-
- $('.dropdown-content a').each(function(i) {
- if (i < saveLabelCount) {
- $(this)
- .get(0)
- .click();
- }
- });
-
- $('.edit-link')
- .get(0)
- .click();
-
- setTimeout(() => {
- expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe(
- 'test 0, test 1, test 2, test 3, test 4',
- );
- done();
- }, 0);
- }, 0);
+ testLabelClicks('test 0, test 1, test 2, test 3, test 4', done);
});
it('changes collapsed tooltip when changing labels when more than 5', done => {
saveLabelCount = 6;
- $('.edit-link')
- .get(0)
- .click();
-
- setTimeout(() => {
- expect($('.dropdown-content a').length).toBe(10);
-
- $('.dropdown-content a').each(function(i) {
- if (i < saveLabelCount) {
- $(this)
- .get(0)
- .click();
- }
- });
-
- $('.edit-link')
- .get(0)
- .click();
-
- setTimeout(() => {
- expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe(
- 'test 0, test 1, test 2, test 3, test 4, and 1 more',
- );
- done();
- }, 0);
- }, 0);
+ testLabelClicks('test 0, test 1, test 2, test 3, test 4, and 1 more', done);
});
});
diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js
index f3fb792c62d..82ab73c2170 100644
--- a/spec/javascripts/lazy_loader_spec.js
+++ b/spec/javascripts/lazy_loader_spec.js
@@ -62,7 +62,7 @@ describe('LazyLoader', function() {
waitForAttributeChange(newImg, ['data-src', 'src']),
])
.then(() => {
- expect(LazyLoader.loadImage).toHaveBeenCalled();
+ expect(LazyLoader.loadImage).toHaveBeenCalledWith(newImg);
expect(newImg.getAttribute('src')).toBe(testPath);
expect(newImg).toHaveClass('js-lazy-loaded');
done();
@@ -79,7 +79,7 @@ describe('LazyLoader', function() {
scrollIntoViewPromise(newImg)
.then(waitForPromises)
.then(() => {
- expect(LazyLoader.loadImage).not.toHaveBeenCalled();
+ expect(LazyLoader.loadImage).not.toHaveBeenCalledWith(newImg);
expect(newImg).not.toHaveClass('js-lazy-loaded');
done();
})
@@ -98,7 +98,7 @@ describe('LazyLoader', function() {
scrollIntoViewPromise(newImg)
.then(waitForPromises)
.then(() => {
- expect(LazyLoader.loadImage).not.toHaveBeenCalled();
+ expect(LazyLoader.loadImage).not.toHaveBeenCalledWith(newImg);
expect(newImg).not.toHaveClass('js-lazy-loaded');
done();
})
@@ -121,7 +121,7 @@ describe('LazyLoader', function() {
])
.then(waitForPromises)
.then(() => {
- expect(LazyLoader.loadImage).toHaveBeenCalled();
+ expect(LazyLoader.loadImage).toHaveBeenCalledWith(newImg);
expect(newImg).toHaveClass('js-lazy-loaded');
done();
})
@@ -156,7 +156,7 @@ describe('LazyLoader', function() {
Promise.all([scrollIntoViewPromise(img), waitForAttributeChange(img, ['data-src', 'src'])])
.then(() => {
- expect(LazyLoader.loadImage).toHaveBeenCalled();
+ expect(LazyLoader.loadImage).toHaveBeenCalledWith(img);
expect(img.getAttribute('src')).toBe(originalDataSrc);
expect(img).toHaveClass('js-lazy-loaded');
done();
@@ -176,7 +176,7 @@ describe('LazyLoader', function() {
waitForAttributeChange(newImg, ['data-src', 'src']),
])
.then(() => {
- expect(LazyLoader.loadImage).toHaveBeenCalled();
+ expect(LazyLoader.loadImage).toHaveBeenCalledWith(newImg);
expect(newImg.getAttribute('src')).toBe(testPath);
expect(newImg).toHaveClass('js-lazy-loaded');
done();
@@ -193,7 +193,7 @@ describe('LazyLoader', function() {
scrollIntoViewPromise(newImg)
.then(waitForPromises)
.then(() => {
- expect(LazyLoader.loadImage).not.toHaveBeenCalled();
+ expect(LazyLoader.loadImage).not.toHaveBeenCalledWith(newImg);
expect(newImg).not.toHaveClass('js-lazy-loaded');
done();
})
@@ -212,7 +212,7 @@ describe('LazyLoader', function() {
scrollIntoViewPromise(newImg)
.then(waitForPromises)
.then(() => {
- expect(LazyLoader.loadImage).not.toHaveBeenCalled();
+ expect(LazyLoader.loadImage).not.toHaveBeenCalledWith(newImg);
expect(newImg).not.toHaveClass('js-lazy-loaded');
done();
})
@@ -234,7 +234,7 @@ describe('LazyLoader', function() {
waitForAttributeChange(newImg, ['data-src', 'src']),
])
.then(() => {
- expect(LazyLoader.loadImage).toHaveBeenCalled();
+ expect(LazyLoader.loadImage).toHaveBeenCalledWith(newImg);
expect(newImg).toHaveClass('js-lazy-loaded');
done();
})
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 85949f2ae86..8956bc92e6b 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -943,4 +943,14 @@ describe('common_utils', () => {
expect(commonUtils.isScopedLabel({ title: 'foobar' })).toBe(false);
});
});
+
+ describe('getDashPath', () => {
+ it('returns the path following /-/', () => {
+ expect(commonUtils.getDashPath('/some/-/url-with-dashes-/')).toEqual('url-with-dashes-/');
+ });
+
+ it('returns null when no path follows /-/', () => {
+ expect(commonUtils.getDashPath('/some/url')).toEqual(null);
+ });
+ });
});
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index a75470b4db8..f8f835ffdef 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-var, prefer-template, no-else-return, dot-notation, no-return-assign, no-new, no-underscore-dangle */
+/* eslint-disable no-var, no-else-return, dot-notation, no-return-assign, no-new, no-underscore-dangle */
import $ from 'jquery';
import LineHighlighter from '~/line_highlighter';
@@ -8,10 +8,10 @@ describe('LineHighlighter', function() {
preloadFixtures('static/line_highlighter.html');
clickLine = function(number, eventData = {}) {
if ($.isEmptyObject(eventData)) {
- return $('#L' + number).click();
+ return $(`#L${number}`).click();
} else {
const e = $.Event('click', eventData);
- return $('#L' + number).trigger(e);
+ return $(`#L${number}`).trigger(e);
}
};
beforeEach(function() {
@@ -42,9 +42,9 @@ describe('LineHighlighter', function() {
var line;
new LineHighlighter({ hash: '#L5-25' });
- expect($('.' + this.css).length).toBe(21);
+ expect($(`.${this.css}`).length).toBe(21);
for (line = 5; line <= 25; line += 1) {
- expect($('#LC' + line)).toHaveClass(this.css);
+ expect($(`#LC${line}`)).toHaveClass(this.css);
}
});
@@ -130,7 +130,7 @@ describe('LineHighlighter', function() {
});
expect($('#LC13')).toHaveClass(this.css);
- expect($('.' + this.css).length).toBe(1);
+ expect($(`.${this.css}`).length).toBe(1);
});
it('sets the hash', function() {
@@ -152,9 +152,9 @@ describe('LineHighlighter', function() {
shiftKey: true,
});
- expect($('.' + this.css).length).toBe(6);
+ expect($(`.${this.css}`).length).toBe(6);
for (line = 15; line <= 20; line += 1) {
- expect($('#LC' + line)).toHaveClass(this.css);
+ expect($(`#LC${line}`)).toHaveClass(this.css);
}
});
@@ -165,9 +165,9 @@ describe('LineHighlighter', function() {
shiftKey: true,
});
- expect($('.' + this.css).length).toBe(6);
+ expect($(`.${this.css}`).length).toBe(6);
for (line = 5; line <= 10; line += 1) {
- expect($('#LC' + line)).toHaveClass(this.css);
+ expect($(`#LC${line}`)).toHaveClass(this.css);
}
});
});
@@ -188,9 +188,9 @@ describe('LineHighlighter', function() {
shiftKey: true,
});
- expect($('.' + this.css).length).toBe(6);
+ expect($(`.${this.css}`).length).toBe(6);
for (line = 5; line <= 10; line += 1) {
- expect($('#LC' + line)).toHaveClass(this.css);
+ expect($(`#LC${line}`)).toHaveClass(this.css);
}
});
@@ -200,9 +200,9 @@ describe('LineHighlighter', function() {
shiftKey: true,
});
- expect($('.' + this.css).length).toBe(6);
+ expect($(`.${this.css}`).length).toBe(6);
for (line = 10; line <= 15; line += 1) {
- expect($('#LC' + line)).toHaveClass(this.css);
+ expect($(`#LC${line}`)).toHaveClass(this.css);
}
});
});
diff --git a/spec/javascripts/monitoring/charts/time_series_spec.js b/spec/javascripts/monitoring/charts/time_series_spec.js
index f6a5ed03c0d..5c718135b90 100644
--- a/spec/javascripts/monitoring/charts/time_series_spec.js
+++ b/spec/javascripts/monitoring/charts/time_series_spec.js
@@ -60,6 +60,18 @@ describe('Time series component', () => {
expect(timeSeriesChart.find('.js-graph-widgets').text()).toBe(mockWidgets);
});
+ it('allows user to override max value label text using prop', () => {
+ timeSeriesChart.setProps({ legendMaxText: 'legendMaxText' });
+
+ expect(timeSeriesChart.props().legendMaxText).toBe('legendMaxText');
+ });
+
+ it('allows user to override average value label text using prop', () => {
+ timeSeriesChart.setProps({ legendAverageText: 'averageText' });
+
+ expect(timeSeriesChart.props().legendAverageText).toBe('averageText');
+ });
+
describe('methods', () => {
describe('formatTooltipText', () => {
const mockDate = deploymentData[0].created_at;
@@ -140,6 +152,16 @@ describe('Time series component', () => {
expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`);
});
});
+
+ it('contains an svg object within an array to properly render icon', () => {
+ timeSeriesChart.vm.$nextTick(() => {
+ expect(timeSeriesChart.vm.chartOptions.dataZoom).toEqual([
+ {
+ handleIcon: `path://${mockSvgPathContent}`,
+ },
+ ]);
+ });
+ });
});
describe('onResize', () => {
diff --git a/spec/javascripts/monitoring/components/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js
index 6ce32d21f45..75df2ce3103 100644
--- a/spec/javascripts/monitoring/components/dashboard_spec.js
+++ b/spec/javascripts/monitoring/components/dashboard_spec.js
@@ -1,9 +1,9 @@
import Vue from 'vue';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlToast } from '@gitlab/ui';
+import VueDraggable from 'vuedraggable';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
-import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
import * as types from '~/monitoring/stores/mutation_types';
import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
@@ -36,6 +36,12 @@ const propsData = {
validateQueryPath: '',
};
+const resetSpy = spy => {
+ if (spy) {
+ spy.calls.reset();
+ }
+};
+
export default propsData;
describe('Dashboard', () => {
@@ -51,11 +57,6 @@ describe('Dashboard', () => {
<div class="layout-page"></div>
`);
- window.gon = {
- ...window.gon,
- ee: false,
- };
-
store = createStore();
mock = new MockAdapter(axios);
DashboardComponent = Vue.extend(Dashboard);
@@ -100,10 +101,15 @@ describe('Dashboard', () => {
});
describe('requests information to the server', () => {
+ let spy;
beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
});
+ afterEach(() => {
+ resetSpy(spy);
+ });
+
it('shows up a loading state', done => {
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
@@ -276,7 +282,7 @@ describe('Dashboard', () => {
});
});
- it('renders the time window dropdown with a set of options', done => {
+ it('renders the datetimepicker dropdown', done => {
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
@@ -286,17 +292,9 @@ describe('Dashboard', () => {
},
store,
});
- const numberOfTimeWindows = Object.keys(timeWindows).length;
setTimeout(() => {
- const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
- const timeWindowDropdownEls = component.$el.querySelectorAll(
- '.js-time-window-dropdown .dropdown-item',
- );
-
- expect(timeWindowDropdown).not.toBeNull();
- expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows);
-
+ expect(component.$el.querySelector('.js-time-window-dropdown')).not.toBeNull();
done();
});
});
@@ -333,8 +331,8 @@ describe('Dashboard', () => {
});
it('shows a specific time window selected from the url params', done => {
- const start = 1564439536;
- const end = 1564441336;
+ const start = '2019-10-01T18:27:47.000Z';
+ const end = '2019-10-01T18:57:47.000Z';
spyOnDependency(Dashboard, 'getTimeDiff').and.returnValue({
start,
end,
@@ -359,7 +357,7 @@ describe('Dashboard', () => {
});
});
- it('defaults to the eight hours time window for non valid url parameters', done => {
+ it('shows an error message if invalid url parameters are passed', done => {
spyOnDependency(Dashboard, 'getParameterValues').and.returnValue([
'<script>alert("XSS")</script>',
]);
@@ -370,15 +368,111 @@ describe('Dashboard', () => {
store,
});
- Vue.nextTick(() => {
- expect(component.selectedTimeWindowKey).toEqual(timeWindowsKeyNames.eightHours);
+ spy = spyOn(component, 'showInvalidDateError');
+ component.$mount();
+ component.$nextTick(() => {
+ expect(component.showInvalidDateError).toHaveBeenCalled();
done();
});
});
});
- // https://gitlab.com/gitlab-org/gitlab-foss/issues/66922
+ describe('drag and drop function', () => {
+ let wrapper;
+ let expectedPanelCount; // also called metrics, naming to be improved: https://gitlab.com/gitlab-org/gitlab/issues/31565
+ const findDraggables = () => wrapper.findAll(VueDraggable);
+ const findEnabledDraggables = () => findDraggables().filter(f => !f.attributes('disabled'));
+ const findDraggablePanels = () => wrapper.findAll('.js-draggable-panel');
+ const findRearrangeButton = () => wrapper.find('.js-rearrange-button');
+
+ beforeEach(done => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+ expectedPanelCount = metricsGroupsAPIResponse.data.reduce(
+ (acc, d) => d.metrics.length + acc,
+ 0,
+ );
+ store.dispatch('monitoringDashboard/setFeatureFlags', { additionalPanelTypesEnabled: true });
+
+ wrapper = shallowMount(DashboardComponent, {
+ localVue,
+ sync: false,
+ propsData: { ...propsData, hasMetrics: true },
+ store,
+ });
+
+ // not using $nextTicket becuase we must wait for the dashboard
+ // to be populated with the mock data results.
+ setTimeout(done);
+ });
+
+ it('wraps vuedraggable', () => {
+ expect(findDraggablePanels().exists()).toBe(true);
+ expect(findDraggablePanels().length).toEqual(expectedPanelCount);
+ });
+
+ it('is disabled by default', () => {
+ expect(findRearrangeButton().exists()).toBe(false);
+ expect(findEnabledDraggables().length).toBe(0);
+ });
+
+ describe('when rearrange is enabled', () => {
+ beforeEach(done => {
+ wrapper.setProps({ rearrangePanelsAvailable: true });
+ wrapper.vm.$nextTick(done);
+ });
+
+ it('displays rearrange button', () => {
+ expect(findRearrangeButton().exists()).toBe(true);
+ });
+
+ describe('when rearrange button is clicked', () => {
+ const findFirstDraggableRemoveButton = () =>
+ findDraggablePanels()
+ .at(0)
+ .find('.js-draggable-remove');
+
+ beforeEach(done => {
+ findRearrangeButton().vm.$emit('click');
+ wrapper.vm.$nextTick(done);
+ });
+
+ it('it enables draggables', () => {
+ expect(findRearrangeButton().attributes('pressed')).toBeTruthy();
+ expect(findEnabledDraggables()).toEqual(findDraggables());
+ });
+
+ it('shows a remove button, which removes a panel', done => {
+ expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false);
+
+ expect(findDraggablePanels().length).toEqual(expectedPanelCount);
+ findFirstDraggableRemoveButton().trigger('click');
+
+ wrapper.vm.$nextTick(() => {
+ // At present graphs will not be removed in backend
+ // See https://gitlab.com/gitlab-org/gitlab/issues/27835
+ expect(findDraggablePanels().length).toEqual(expectedPanelCount - 1);
+ done();
+ });
+ });
+
+ it('it disables draggables when clicked again', done => {
+ findRearrangeButton().vm.$emit('click');
+ wrapper.vm.$nextTick(() => {
+ expect(findRearrangeButton().attributes('pressed')).toBeFalsy();
+ expect(findEnabledDraggables().length).toBe(0);
+ done();
+ });
+ });
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ });
+
+ // https://gitlab.com/gitlab-org/gitlab-ce/issues/66922
// eslint-disable-next-line jasmine/no-disabled-tests
xdescribe('link to chart', () => {
let wrapper;
@@ -527,7 +621,6 @@ describe('Dashboard', () => {
component.$store.dispatch('monitoringDashboard/setFeatureFlags', {
prometheusEndpoint: false,
- multipleDashboardsEnabled: true,
});
component.$store.commit(
diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js
index 955a39e03a5..1bd74f59282 100644
--- a/spec/javascripts/monitoring/store/actions_spec.js
+++ b/spec/javascripts/monitoring/store/actions_spec.js
@@ -240,8 +240,6 @@ describe('Monitoring store actions', () => {
const response = metricsDashboardResponse;
response.all_dashboards = dashboardGitResponse;
- state.multipleDashboardsEnabled = true;
-
receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params });
expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse);
diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js
index bdb68a80a8a..bdddd83358c 100644
--- a/spec/javascripts/monitoring/store/mutations_spec.js
+++ b/spec/javascripts/monitoring/store/mutations_spec.js
@@ -7,6 +7,7 @@ import {
metricsDashboardResponse,
dashboardGitResponse,
} from '../mock_data';
+import { uniqMetricsId } from '~/monitoring/stores/utils';
describe('Monitoring mutations', () => {
let stateCopy;
@@ -128,6 +129,7 @@ describe('Monitoring mutations', () => {
describe('SET_QUERY_RESULT', () => {
const metricId = 12;
+ const id = 'system_metrics_kubernetes_container_memory_total';
const result = [{ values: [[0, 1], [1, 1], [1, 3]] }];
beforeEach(() => {
@@ -146,12 +148,13 @@ describe('Monitoring mutations', () => {
});
it('sets metricsWithData value', () => {
+ const uniqId = uniqMetricsId({ metric_id: metricId, id });
mutations[types.SET_QUERY_RESULT](stateCopy, {
- metricId,
+ metricId: uniqId,
result,
});
- expect(stateCopy.metricsWithData).toEqual([12]);
+ expect(stateCopy.metricsWithData).toEqual([uniqId]);
});
it('does not store empty results', () => {
diff --git a/spec/javascripts/monitoring/store/utils_spec.js b/spec/javascripts/monitoring/store/utils_spec.js
index 73dd370ffb3..98388ac19f8 100644
--- a/spec/javascripts/monitoring/store/utils_spec.js
+++ b/spec/javascripts/monitoring/store/utils_spec.js
@@ -1,4 +1,4 @@
-import { groupQueriesByChartInfo } from '~/monitoring/stores/utils';
+import { groupQueriesByChartInfo, normalizeMetric, uniqMetricsId } from '~/monitoring/stores/utils';
describe('groupQueriesByChartInfo', () => {
let input;
@@ -12,7 +12,11 @@ describe('groupQueriesByChartInfo', () => {
];
output = [
- { title: 'title', y_label: 'MB', queries: [{ metricId: null }, { metricId: null }] },
+ {
+ title: 'title',
+ y_label: 'MB',
+ queries: [{ metricId: null }, { metricId: null }],
+ },
{ title: 'new title', y_label: 'MB', queries: [{ metricId: null }] },
];
@@ -35,3 +39,36 @@ describe('groupQueriesByChartInfo', () => {
expect(groupQueriesByChartInfo(input)).toEqual(output);
});
});
+
+describe('normalizeMetric', () => {
+ [
+ { args: [], expected: 'undefined_undefined' },
+ { args: [undefined], expected: 'undefined_undefined' },
+ { args: [{ id: 'something' }], expected: 'undefined_something' },
+ { args: [{ id: 45 }], expected: 'undefined_45' },
+ { args: [{ metric_id: 5 }], expected: '5_undefined' },
+ { args: [{ metric_id: 'something' }], expected: 'something_undefined' },
+ {
+ args: [{ metric_id: 5, id: 'system_metrics_kubernetes_container_memory_total' }],
+ expected: '5_system_metrics_kubernetes_container_memory_total',
+ },
+ ].forEach(({ args, expected }) => {
+ it(`normalizes metric to "${expected}" with args=${JSON.stringify(args)}`, () => {
+ expect(normalizeMetric(...args)).toEqual({ metric_id: expected });
+ });
+ });
+});
+
+describe('uniqMetricsId', () => {
+ [
+ { input: { id: 1 }, expected: 'undefined_1' },
+ { input: { metric_id: 2 }, expected: '2_undefined' },
+ { input: { metric_id: 2, id: 21 }, expected: '2_21' },
+ { input: { metric_id: 22, id: 1 }, expected: '22_1' },
+ { input: { metric_id: 'aaa', id: '_a' }, expected: 'aaa__a' },
+ ].forEach(({ input, expected }) => {
+ it(`creates unique metric ID with ${JSON.stringify(input)}`, () => {
+ expect(uniqMetricsId(input)).toEqual(expected);
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js
index e22e8cdc03d..512dd2a0eb3 100644
--- a/spec/javascripts/monitoring/utils_spec.js
+++ b/spec/javascripts/monitoring/utils_spec.js
@@ -1,5 +1,14 @@
-import { getTimeDiff, graphDataValidatorForValues } from '~/monitoring/utils';
-import { timeWindows } from '~/monitoring/constants';
+import {
+ getTimeDiff,
+ getTimeWindow,
+ graphDataValidatorForValues,
+ isDateTimePickerInputValid,
+ truncateZerosInDateTime,
+ stringToISODate,
+ ISODateToString,
+ isValidDate,
+} from '~/monitoring/utils';
+import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data';
describe('getTimeDiff', () => {
@@ -39,6 +48,55 @@ describe('getTimeDiff', () => {
});
});
+describe('getTimeWindow', () => {
+ [
+ {
+ args: [
+ {
+ start: '2019-10-01T18:27:47.000Z',
+ end: '2019-10-01T21:27:47.000Z',
+ },
+ ],
+ expected: timeWindowsKeyNames.threeHours,
+ },
+ {
+ args: [
+ {
+ start: '2019-10-01T28:27:47.000Z',
+ end: '2019-10-01T21:27:47.000Z',
+ },
+ ],
+ expected: null,
+ },
+ {
+ args: [
+ {
+ start: '',
+ end: '',
+ },
+ ],
+ expected: null,
+ },
+ {
+ args: [
+ {
+ start: null,
+ end: null,
+ },
+ ],
+ expected: null,
+ },
+ {
+ args: [{}],
+ expected: null,
+ },
+ ].forEach(({ args, expected }) => {
+ it(`returns "${expected}" with args=${JSON.stringify(args)}`, () => {
+ expect(getTimeWindow(...args)).toEqual(expected);
+ });
+ });
+});
+
describe('graphDataValidatorForValues', () => {
/*
* When dealing with a metric using the query format, e.g.
@@ -62,3 +120,190 @@ describe('graphDataValidatorForValues', () => {
expect(validGraphData).toBe(true);
});
});
+
+describe('stringToISODate', () => {
+ ['', 'null', undefined, 'abc'].forEach(input => {
+ it(`throws error for invalid input like ${input}`, done => {
+ try {
+ stringToISODate(input);
+ } catch (e) {
+ expect(e).toBeDefined();
+ done();
+ }
+ });
+ });
+ [
+ {
+ input: '2019-09-09 01:01:01',
+ output: '2019-09-09T01:01:01Z',
+ },
+ {
+ input: '2019-09-09 00:00:00',
+ output: '2019-09-09T00:00:00Z',
+ },
+ {
+ input: '2019-09-09 23:59:59',
+ output: '2019-09-09T23:59:59Z',
+ },
+ {
+ input: '2019-09-09',
+ output: '2019-09-09T00:00:00Z',
+ },
+ ].forEach(({ input, output }) => {
+ it(`returns ${output} from ${input}`, () => {
+ expect(stringToISODate(input)).toBe(output);
+ });
+ });
+});
+
+describe('ISODateToString', () => {
+ [
+ {
+ input: new Date('2019-09-09T00:00:00.000Z'),
+ output: '2019-09-09 00:00:00',
+ },
+ {
+ input: new Date('2019-09-09T07:00:00.000Z'),
+ output: '2019-09-09 07:00:00',
+ },
+ ].forEach(({ input, output }) => {
+ it(`ISODateToString return ${output} for ${input}`, () => {
+ expect(ISODateToString(input)).toBe(output);
+ });
+ });
+});
+
+describe('truncateZerosInDateTime', () => {
+ [
+ {
+ input: '',
+ output: '',
+ },
+ {
+ input: '2019-10-10',
+ output: '2019-10-10',
+ },
+ {
+ input: '2019-10-10 00:00:01',
+ output: '2019-10-10 00:00:01',
+ },
+ {
+ input: '2019-10-10 00:00:00',
+ output: '2019-10-10',
+ },
+ ].forEach(({ input, output }) => {
+ it(`truncateZerosInDateTime return ${output} for ${input}`, () => {
+ expect(truncateZerosInDateTime(input)).toBe(output);
+ });
+ });
+});
+
+describe('isValidDate', () => {
+ [
+ {
+ input: '2019-09-09T00:00:00.000Z',
+ output: true,
+ },
+ {
+ input: '2019-09-09T000:00.000Z',
+ output: false,
+ },
+ {
+ input: 'a2019-09-09T000:00.000Z',
+ output: false,
+ },
+ {
+ input: '2019-09-09T',
+ output: false,
+ },
+ {
+ input: '2019-09-09',
+ output: true,
+ },
+ {
+ input: '2019-9-9',
+ output: true,
+ },
+ {
+ input: '2019-9-',
+ output: true,
+ },
+ {
+ input: '2019--',
+ output: false,
+ },
+ {
+ input: '2019',
+ output: true,
+ },
+ {
+ input: '',
+ output: false,
+ },
+ {
+ input: null,
+ output: false,
+ },
+ ].forEach(({ input, output }) => {
+ it(`isValidDate return ${output} for ${input}`, () => {
+ expect(isValidDate(input)).toBe(output);
+ });
+ });
+});
+
+describe('isDateTimePickerInputValid', () => {
+ [
+ {
+ input: null,
+ output: false,
+ },
+ {
+ input: '',
+ output: false,
+ },
+ {
+ input: 'xxxx-xx-xx',
+ output: false,
+ },
+ {
+ input: '9999-99-19',
+ output: false,
+ },
+ {
+ input: '2019-19-23',
+ output: false,
+ },
+ {
+ input: '2019-09-23',
+ output: true,
+ },
+ {
+ input: '2019-09-23 x',
+ output: false,
+ },
+ {
+ input: '2019-09-29 0:0:0',
+ output: false,
+ },
+ {
+ input: '2019-09-29 00:00:00',
+ output: true,
+ },
+ {
+ input: '2019-09-29 24:24:24',
+ output: false,
+ },
+ {
+ input: '2019-09-29 23:24:24',
+ output: true,
+ },
+ {
+ input: '2019-09-29 23:24:24 ',
+ output: false,
+ },
+ ].forEach(({ input, output }) => {
+ it(`returns ${output} for ${input}`, () => {
+ expect(isDateTimePickerInputValid(input)).toBe(output);
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js
index 1c366aee8e2..7524de36ac5 100644
--- a/spec/javascripts/notes/components/discussion_filter_spec.js
+++ b/spec/javascripts/notes/components/discussion_filter_spec.js
@@ -160,5 +160,28 @@ describe('DiscussionFilter component', () => {
done();
});
});
+
+ it('fetches discussions when there is a hash', done => {
+ window.location.hash = `note_${discussionMock.notes[0].id}`;
+ vm.currentValue = discussionFiltersMock[2].value;
+ spyOn(vm, 'selectFilter');
+ vm.handleLocationHash();
+
+ vm.$nextTick(() => {
+ expect(vm.selectFilter).toHaveBeenCalled();
+ done();
+ });
+ });
+
+ it('does not fetch discussions when there is no hash', done => {
+ window.location.hash = '';
+ spyOn(vm, 'selectFilter');
+ vm.handleLocationHash();
+
+ vm.$nextTick(() => {
+ expect(vm.selectFilter).not.toHaveBeenCalled();
+ done();
+ });
+ });
});
});
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index 4a640d589fb..ade4725dd68 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -48,9 +48,22 @@ describe('Notes Store mutations', () => {
});
describe('ADD_NEW_REPLY_TO_DISCUSSION', () => {
+ const newReply = Object.assign({}, note, { discussion_id: discussionMock.id });
+
+ let state;
+
+ beforeEach(() => {
+ state = { discussions: [{ ...discussionMock }] };
+ });
+
it('should add a reply to a specific discussion', () => {
- const state = { discussions: [discussionMock] };
- const newReply = Object.assign({}, note, { discussion_id: discussionMock.id });
+ mutations.ADD_NEW_REPLY_TO_DISCUSSION(state, newReply);
+
+ expect(state.discussions[0].notes.length).toEqual(4);
+ });
+
+ it('should not add the note if it already exists in the discussion', () => {
+ mutations.ADD_NEW_REPLY_TO_DISCUSSION(state, newReply);
mutations.ADD_NEW_REPLY_TO_DISCUSSION(state, newReply);
expect(state.discussions[0].notes.length).toEqual(4);
diff --git a/spec/javascripts/pager_spec.js b/spec/javascripts/pager_spec.js
index 93efc139254..c95a8400c6c 100644
--- a/spec/javascripts/pager_spec.js
+++ b/spec/javascripts/pager_spec.js
@@ -63,9 +63,9 @@ describe('pager', () => {
describe('getOld', () => {
const urlRegex = /(.*)some_list(.*)$/;
- function mockSuccess() {
+ function mockSuccess(count = 0) {
axiosMock.onGet(urlRegex).reply(200, {
- count: 0,
+ count,
html: '',
});
}
@@ -142,5 +142,21 @@ describe('pager', () => {
done();
});
});
+
+ it('disables if return count is less than limit', done => {
+ Pager.offset = 0;
+ Pager.limit = 20;
+
+ mockSuccess(1);
+ spyOn(Pager.loading, 'hide');
+ Pager.getOld();
+
+ setTimeout(() => {
+ expect(Pager.loading.hide).toHaveBeenCalled();
+ expect(Pager.disable).toBe(true);
+
+ done();
+ });
+ });
});
});
diff --git a/spec/javascripts/performance_bar/components/detailed_metric_spec.js b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
deleted file mode 100644
index 0486b5fa3db..00000000000
--- a/spec/javascripts/performance_bar/components/detailed_metric_spec.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import Vue from 'vue';
-import detailedMetric from '~/performance_bar/components/detailed_metric.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('detailedMetric', () => {
- let vm;
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('when the current request has no details', () => {
- beforeEach(() => {
- vm = mountComponent(Vue.extend(detailedMetric), {
- currentRequest: {},
- metric: 'gitaly',
- header: 'Gitaly calls',
- details: 'details',
- keys: ['feature', 'request'],
- });
- });
-
- it('does not render the element', () => {
- expect(vm.$el.innerHTML).toEqual(undefined);
- });
- });
-
- describe('when the current request has details', () => {
- const requestDetails = [
- { duration: '100', feature: 'find_commit', request: 'abcdef', backtrace: ['hello', 'world'] },
- { duration: '23', feature: 'rebase_in_progress', request: '', backtrace: ['world', 'hello'] },
- ];
-
- beforeEach(() => {
- vm = mountComponent(Vue.extend(detailedMetric), {
- currentRequest: {
- details: {
- gitaly: {
- duration: '123ms',
- calls: '456',
- details: requestDetails,
- },
- },
- },
- metric: 'gitaly',
- header: 'Gitaly calls',
- keys: ['feature', 'request'],
- });
- });
-
- it('diplays details', () => {
- expect(vm.$el.innerText.replace(/\s+/g, ' ')).toContain('123ms / 456');
- });
-
- it('adds a modal with a table of the details', () => {
- vm.$el
- .querySelectorAll('.performance-bar-modal td:nth-child(1)')
- .forEach((duration, index) => {
- expect(duration.innerText).toContain(requestDetails[index].duration);
- });
-
- vm.$el
- .querySelectorAll('.performance-bar-modal td:nth-child(2)')
- .forEach((feature, index) => {
- expect(feature.innerText).toContain(requestDetails[index].feature);
- });
-
- vm.$el
- .querySelectorAll('.performance-bar-modal td:nth-child(2)')
- .forEach((request, index) => {
- expect(request.innerText).toContain(requestDetails[index].request);
- });
-
- expect(vm.$el.querySelector('.text-expander.js-toggle-button')).not.toBeNull();
-
- vm.$el.querySelectorAll('.performance-bar-modal td:nth-child(2)').forEach(request => {
- expect(request.innerText).toContain('world');
- });
- });
-
- it('displays the metric title', () => {
- expect(vm.$el.innerText).toContain('gitaly');
- });
-
- describe('when using a custom metric title', () => {
- beforeEach(() => {
- vm = mountComponent(Vue.extend(detailedMetric), {
- currentRequest: {
- details: {
- gitaly: {
- duration: '123ms',
- calls: '456',
- details: requestDetails,
- },
- },
- },
- metric: 'gitaly',
- title: 'custom',
- header: 'Gitaly calls',
- keys: ['feature', 'request'],
- });
- });
-
- it('displays the custom title', () => {
- expect(vm.$el.innerText).toContain('custom');
- });
- });
- });
-});
diff --git a/spec/javascripts/performance_bar/components/performance_bar_app_spec.js b/spec/javascripts/performance_bar/components/performance_bar_app_spec.js
deleted file mode 100644
index 7926db44429..00000000000
--- a/spec/javascripts/performance_bar/components/performance_bar_app_spec.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import Vue from 'vue';
-import performanceBarApp from '~/performance_bar/components/performance_bar_app.vue';
-import PerformanceBarStore from '~/performance_bar/stores/performance_bar_store';
-
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('performance bar app', () => {
- let vm;
-
- beforeEach(() => {
- const store = new PerformanceBarStore();
-
- vm = mountComponent(Vue.extend(performanceBarApp), {
- store,
- env: 'development',
- requestId: '123',
- peekUrl: '/-/peek/results',
- profileUrl: '?lineprofiler=true',
- });
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('sets the class to match the environment', () => {
- expect(vm.$el.getAttribute('class')).toContain('development');
- });
-});
diff --git a/spec/javascripts/performance_bar/components/request_selector_spec.js b/spec/javascripts/performance_bar/components/request_selector_spec.js
deleted file mode 100644
index 3c2169de877..00000000000
--- a/spec/javascripts/performance_bar/components/request_selector_spec.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import Vue from 'vue';
-import requestSelector from '~/performance_bar/components/request_selector.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('request selector', () => {
- const requests = [
- { id: '123', url: 'https://gitlab.com/' },
- {
- id: '456',
- url: 'https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/1',
- },
- {
- id: '789',
- url: 'https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/1.json?serializer=widget',
- },
- ];
-
- let vm;
-
- beforeEach(() => {
- vm = mountComponent(Vue.extend(requestSelector), {
- requests,
- currentRequest: requests[1],
- });
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- function optionText(requestId) {
- return vm.$el.querySelector(`[value='${requestId}']`).innerText.trim();
- }
-
- it('displays the last component of the path', () => {
- expect(optionText(requests[2].id)).toEqual('1.json?serializer=widget');
- });
-
- it('keeps the last two components of the path when the last component is numeric', () => {
- expect(optionText(requests[1].id)).toEqual('merge_requests/1');
- });
-
- it('ignores trailing slashes', () => {
- expect(optionText(requests[0].id)).toEqual('gitlab.com');
- });
-});
diff --git a/spec/javascripts/pipelines/graph/graph_component_spec.js b/spec/javascripts/pipelines/graph/graph_component_spec.js
index 98e92aff25f..5effbaabcd1 100644
--- a/spec/javascripts/pipelines/graph/graph_component_spec.js
+++ b/spec/javascripts/pipelines/graph/graph_component_spec.js
@@ -1,10 +1,17 @@
import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import PipelineStore from '~/pipelines/stores/pipeline_store';
import graphComponent from '~/pipelines/components/graph/graph_component.vue';
import graphJSON from './mock_data';
+import linkedPipelineJSON from '../linked_pipelines_mock.json';
+import PipelinesMediator from '~/pipelines/pipeline_details_mediator';
describe('graph component', () => {
const GraphComponent = Vue.extend(graphComponent);
+ const store = new PipelineStore();
+ store.storePipeline(linkedPipelineJSON);
+ const mediator = new PipelinesMediator({ endpoint: '' });
+
let component;
beforeEach(() => {
@@ -22,6 +29,7 @@ describe('graph component', () => {
component = mountComponent(GraphComponent, {
isLoading: true,
pipeline: {},
+ mediator,
});
expect(component.$el.querySelector('.loading-icon')).toBeDefined();
@@ -33,6 +41,7 @@ describe('graph component', () => {
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline: graphJSON,
+ mediator,
});
expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
@@ -57,11 +66,205 @@ describe('graph component', () => {
});
});
+ describe('when linked pipelines are present', () => {
+ beforeEach(() => {
+ component = mountComponent(GraphComponent, {
+ isLoading: false,
+ pipeline: store.state.pipeline,
+ mediator,
+ });
+ });
+
+ describe('rendered output', () => {
+ it('should include the pipelines graph', () => {
+ expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true);
+ });
+
+ it('should not include the loading icon', () => {
+ expect(component.$el.querySelector('.fa-spinner')).toBeNull();
+ });
+
+ it('should include the stage column list', () => {
+ expect(component.$el.querySelector('.stage-column-list')).not.toBeNull();
+ });
+
+ it('should include the no-margin class on the first child', () => {
+ const firstStageColumnElement = component.$el.querySelector(
+ '.stage-column-list .stage-column',
+ );
+
+ expect(firstStageColumnElement.classList.contains('no-margin')).toEqual(true);
+ });
+
+ it('should include the has-only-one-job class on the first child', () => {
+ const firstStageColumnElement = component.$el.querySelector(
+ '.stage-column-list .stage-column',
+ );
+
+ expect(firstStageColumnElement.classList.contains('has-only-one-job')).toEqual(true);
+ });
+
+ it('should include the left-margin class on the second child', () => {
+ const firstStageColumnElement = component.$el.querySelector(
+ '.stage-column-list .stage-column:last-child',
+ );
+
+ expect(firstStageColumnElement.classList.contains('left-margin')).toEqual(true);
+ });
+
+ it('should include the js-has-linked-pipelines flag', () => {
+ expect(component.$el.querySelector('.js-has-linked-pipelines')).not.toBeNull();
+ });
+ });
+
+ describe('computeds and methods', () => {
+ describe('capitalizeStageName', () => {
+ it('it capitalizes the stage name', () => {
+ expect(component.capitalizeStageName('mystage')).toBe('Mystage');
+ });
+ });
+
+ describe('stageConnectorClass', () => {
+ it('it returns left-margin when there is a triggerer', () => {
+ expect(component.stageConnectorClass(0, { groups: ['job'] })).toBe('no-margin');
+ });
+ });
+ });
+
+ describe('linked pipelines components', () => {
+ beforeEach(() => {
+ component = mountComponent(GraphComponent, {
+ isLoading: false,
+ pipeline: store.state.pipeline,
+ mediator,
+ });
+ });
+
+ it('should render an upstream pipelines column', () => {
+ expect(component.$el.querySelector('.linked-pipelines-column')).not.toBeNull();
+ expect(component.$el.innerHTML).toContain('Upstream');
+ });
+
+ it('should render a downstream pipelines column', () => {
+ expect(component.$el.querySelector('.linked-pipelines-column')).not.toBeNull();
+ expect(component.$el.innerHTML).toContain('Downstream');
+ });
+
+ describe('triggered by', () => {
+ describe('on click', () => {
+ it('should emit `onClickTriggeredBy` when triggered by linked pipeline is clicked', () => {
+ spyOn(component, '$emit');
+
+ component.$el.querySelector('#js-linked-pipeline-12').click();
+
+ expect(component.$emit).toHaveBeenCalledWith(
+ 'onClickTriggeredBy',
+ component.pipeline,
+ component.pipeline.triggered_by[0],
+ );
+ });
+ });
+
+ describe('with expanded pipeline', () => {
+ it('should render expanded pipeline', done => {
+ // expand the pipeline
+ store.state.pipeline.triggered_by[0].isExpanded = true;
+
+ component = mountComponent(GraphComponent, {
+ isLoading: false,
+ pipeline: store.state.pipeline,
+ mediator,
+ });
+
+ Vue.nextTick()
+ .then(() => {
+ expect(component.$el.querySelector('.js-upstream-pipeline-12')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('triggered', () => {
+ describe('on click', () => {
+ it('should emit `onClickTriggered`', () => {
+ spyOn(component, '$emit');
+
+ component.$el.querySelector('#js-linked-pipeline-34993051').click();
+
+ expect(component.$emit).toHaveBeenCalledWith(
+ 'onClickTriggered',
+ component.pipeline,
+ component.pipeline.triggered[0],
+ );
+ });
+ });
+
+ describe('with expanded pipeline', () => {
+ it('should render expanded pipeline', done => {
+ // expand the pipeline
+ store.state.pipeline.triggered[0].isExpanded = true;
+
+ component = mountComponent(GraphComponent, {
+ isLoading: false,
+ pipeline: store.state.pipeline,
+ mediator,
+ });
+
+ Vue.nextTick()
+ .then(() => {
+ expect(
+ component.$el.querySelector('.js-downstream-pipeline-34993051'),
+ ).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+ });
+ });
+
+ describe('when linked pipelines are not present', () => {
+ beforeEach(() => {
+ const pipeline = Object.assign(linkedPipelineJSON, { triggered: null, triggered_by: null });
+ component = mountComponent(GraphComponent, {
+ isLoading: false,
+ pipeline,
+ mediator,
+ });
+ });
+
+ describe('rendered output', () => {
+ it('should include the first column with a no margin', () => {
+ const firstColumn = component.$el.querySelector('.stage-column:first-child');
+
+ expect(firstColumn.classList.contains('no-margin')).toEqual(true);
+ });
+
+ it('should not render a linked pipelines column', () => {
+ expect(component.$el.querySelector('.linked-pipelines-column')).toBeNull();
+ });
+ });
+
+ describe('stageConnectorClass', () => {
+ it('it returns left-margin when no triggerer and there is one job', () => {
+ expect(component.stageConnectorClass(0, { groups: ['job'] })).toBe('no-margin');
+ });
+
+ it('it returns left-margin when no triggerer and not the first stage', () => {
+ expect(component.stageConnectorClass(99, { groups: ['job'] })).toBe('left-margin');
+ });
+ });
+ });
+
describe('capitalizeStageName', () => {
it('capitalizes and escapes stage name', () => {
component = mountComponent(GraphComponent, {
isLoading: false,
pipeline: graphJSON,
+ mediator,
});
expect(
diff --git a/spec/javascripts/pipelines/graph/linked_pipeline_spec.js b/spec/javascripts/pipelines/graph/linked_pipeline_spec.js
new file mode 100644
index 00000000000..8d3abf094b6
--- /dev/null
+++ b/spec/javascripts/pipelines/graph/linked_pipeline_spec.js
@@ -0,0 +1,116 @@
+import Vue from 'vue';
+import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mockData from './linked_pipelines_mock_data';
+
+const mockPipeline = mockData.triggered[0];
+
+describe('Linked pipeline', () => {
+ const Component = Vue.extend(LinkedPipelineComponent);
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('rendered output', () => {
+ const props = {
+ pipeline: mockPipeline,
+ };
+
+ beforeEach(() => {
+ vm = mountComponent(Component, props);
+ });
+
+ it('should render a list item as the containing element', () => {
+ expect(vm.$el.tagName).toBe('LI');
+ });
+
+ it('should render a button', () => {
+ const linkElement = vm.$el.querySelector('.js-linked-pipeline-content');
+
+ expect(linkElement).not.toBeNull();
+ });
+
+ it('should render the project name', () => {
+ expect(vm.$el.innerText).toContain(props.pipeline.project.name);
+ });
+
+ it('should render an svg within the status container', () => {
+ const pipelineStatusElement = vm.$el.querySelector('.js-linked-pipeline-status');
+
+ expect(pipelineStatusElement.querySelector('svg')).not.toBeNull();
+ });
+
+ it('should render the pipeline status icon svg', () => {
+ expect(vm.$el.querySelector('.js-ci-status-icon-running')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-ci-status-icon-running').innerHTML).toContain('<svg');
+ });
+
+ it('should have a ci-status child component', () => {
+ expect(vm.$el.querySelector('.js-linked-pipeline-status')).not.toBeNull();
+ });
+
+ it('should render the pipeline id', () => {
+ expect(vm.$el.innerText).toContain(`#${props.pipeline.id}`);
+ });
+
+ it('should correctly compute the tooltip text', () => {
+ expect(vm.tooltipText).toContain(mockPipeline.project.name);
+ expect(vm.tooltipText).toContain(mockPipeline.details.status.label);
+ });
+
+ it('should render the tooltip text as the title attribute', () => {
+ const tooltipRef = vm.$el.querySelector('.js-linked-pipeline-content');
+ const titleAttr = tooltipRef.getAttribute('data-original-title');
+
+ expect(titleAttr).toContain(mockPipeline.project.name);
+ expect(titleAttr).toContain(mockPipeline.details.status.label);
+ });
+
+ it('does not render the loading icon when isLoading is false', () => {
+ expect(vm.$el.querySelector('.js-linked-pipeline-loading')).toBeNull();
+ });
+ });
+
+ describe('when isLoading is true', () => {
+ const props = {
+ pipeline: { ...mockPipeline, isLoading: true },
+ };
+
+ beforeEach(() => {
+ vm = mountComponent(Component, props);
+ });
+
+ it('renders a loading icon', () => {
+ expect(vm.$el.querySelector('.js-linked-pipeline-loading')).not.toBeNull();
+ });
+ });
+
+ describe('on click', () => {
+ const props = {
+ pipeline: mockPipeline,
+ };
+
+ beforeEach(() => {
+ vm = mountComponent(Component, props);
+ });
+
+ it('emits `pipelineClicked` event', () => {
+ spyOn(vm, '$emit');
+ vm.$el.querySelector('button').click();
+
+ expect(vm.$emit).toHaveBeenCalledWith('pipelineClicked');
+ });
+
+ it('should emit `bv::hide::tooltip` to close the tooltip', () => {
+ spyOn(vm.$root, '$emit');
+ vm.$el.querySelector('button').click();
+
+ expect(vm.$root.$emit.calls.argsFor(0)).toEqual([
+ 'bv::hide::tooltip',
+ 'js-linked-pipeline-132',
+ ]);
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/graph/linked_pipelines_column_spec.js b/spec/javascripts/pipelines/graph/linked_pipelines_column_spec.js
new file mode 100644
index 00000000000..1f835dc4dee
--- /dev/null
+++ b/spec/javascripts/pipelines/graph/linked_pipelines_column_spec.js
@@ -0,0 +1,38 @@
+import Vue from 'vue';
+import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mockData from './linked_pipelines_mock_data';
+
+describe('Linked Pipelines Column', () => {
+ const Component = Vue.extend(LinkedPipelinesColumn);
+ const props = {
+ columnTitle: 'Upstream',
+ linkedPipelines: mockData.triggered,
+ graphPosition: 'right',
+ };
+ let vm;
+
+ beforeEach(() => {
+ vm = mountComponent(Component, props);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders the pipeline orientation', () => {
+ const titleElement = vm.$el.querySelector('.linked-pipelines-column-title');
+
+ expect(titleElement.innerText).toContain(props.columnTitle);
+ });
+
+ it('has the correct number of linked pipeline child components', () => {
+ expect(vm.$children.length).toBe(props.linkedPipelines.length);
+ });
+
+ it('renders the correct number of linked pipelines', () => {
+ const linkedPipelineElements = vm.$el.querySelectorAll('.linked-pipeline');
+
+ expect(linkedPipelineElements.length).toBe(props.linkedPipelines.length);
+ });
+});
diff --git a/spec/javascripts/pipelines/graph/linked_pipelines_mock_data.js b/spec/javascripts/pipelines/graph/linked_pipelines_mock_data.js
new file mode 100644
index 00000000000..f794b8484a7
--- /dev/null
+++ b/spec/javascripts/pipelines/graph/linked_pipelines_mock_data.js
@@ -0,0 +1,407 @@
+export default {
+ triggered_by: {
+ id: 129,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/pipelines/129',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/pipelines/129',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: '7-5-stable',
+ path: '/gitlab-org/gitlab-foss/commits/7-5-stable',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: '23433d4d8b20d7e45c103d0b6048faad38a130ab',
+ short_id: '23433d4d',
+ title: 'Version 7.5.0.rc1',
+ created_at: '2014-11-17T15:44:14.000+01:00',
+ parent_ids: ['30ac909f30f58d319b42ed1537664483894b18cd'],
+ message: 'Version 7.5.0.rc1\n',
+ author_name: 'Jacob Vosmaer',
+ author_email: 'contact@jacobvosmaer.nl',
+ authored_date: '2014-11-17T15:44:14.000+01:00',
+ committer_name: 'Jacob Vosmaer',
+ committer_email: 'contact@jacobvosmaer.nl',
+ committed_date: '2014-11-17T15:44:14.000+01:00',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/e66d11c0eedf8c07b3b18fca46599807?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab',
+ commit_path: '/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/pipelines/129/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/pipelines/129/cancel',
+ created_at: '2017-05-24T14:46:20.090Z',
+ updated_at: '2017-05-24T14:46:29.906Z',
+ },
+ triggered: [
+ {
+ id: 132,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/pipelines/132',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/pipelines/132',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: 'crowd',
+ path: '/gitlab-org/gitlab-foss/commits/crowd',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+ short_id: 'b9d58c4c',
+ title: 'getting user keys publically through http without any authentication, the github…',
+ created_at: '2013-10-03T12:50:33.000+05:30',
+ parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
+ message:
+ 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n\nchangelog updated to include ssh key retrieval feature update\n',
+ author_name: 'devaroop',
+ author_email: 'devaroop123@yahoo.co.in',
+ authored_date: '2013-10-02T20:39:29.000+05:30',
+ committer_name: 'devaroop',
+ committer_email: 'devaroop123@yahoo.co.in',
+ committed_date: '2013-10-03T12:50:33.000+05:30',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+ commit_path: '/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/pipelines/132/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/pipelines/132/cancel',
+ created_at: '2017-05-24T14:46:24.644Z',
+ updated_at: '2017-05-24T14:48:55.226Z',
+ },
+ {
+ id: 133,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/pipelines/133',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/pipelines/133',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: 'crowd',
+ path: '/gitlab-org/gitlab-foss/commits/crowd',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+ short_id: 'b6bd4856',
+ title: 'getting user keys publically through http without any authentication, the github…',
+ created_at: '2013-10-02T20:39:29.000+05:30',
+ parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
+ message:
+ 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n',
+ author_name: 'devaroop',
+ author_email: 'devaroop123@yahoo.co.in',
+ authored_date: '2013-10-02T20:39:29.000+05:30',
+ committer_name: 'devaroop',
+ committer_email: 'devaroop123@yahoo.co.in',
+ committed_date: '2013-10-02T20:39:29.000+05:30',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+ commit_path: '/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/pipelines/133/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/pipelines/133/cancel',
+ created_at: '2017-05-24T14:46:24.648Z',
+ updated_at: '2017-05-24T14:48:59.673Z',
+ },
+ {
+ id: 130,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/pipelines/130',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/pipelines/130',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: 'crowd',
+ path: '/gitlab-org/gitlab-foss/commits/crowd',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: '6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+ short_id: '6d7ced4a',
+ title: 'Whitespace fixes to patch',
+ created_at: '2013-10-08T13:53:22.000-05:00',
+ parent_ids: ['1875141a963a4238bda29011d8f7105839485253'],
+ message: 'Whitespace fixes to patch\n',
+ author_name: 'Dale Hamel',
+ author_email: 'dale.hamel@srvthe.net',
+ authored_date: '2013-10-08T13:53:22.000-05:00',
+ committer_name: 'Dale Hamel',
+ committer_email: 'dale.hamel@invenia.ca',
+ committed_date: '2013-10-08T13:53:22.000-05:00',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/cd08930e69fa5ad1a669206e7bafe476?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+ commit_path: '/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/pipelines/130/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/pipelines/130/cancel',
+ created_at: '2017-05-24T14:46:24.630Z',
+ updated_at: '2017-05-24T14:49:45.091Z',
+ },
+ {
+ id: 131,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/pipelines/132',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/pipelines/132',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: 'crowd',
+ path: '/gitlab-org/gitlab-foss/commits/crowd',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+ short_id: 'b9d58c4c',
+ title: 'getting user keys publically through http without any authentication, the github…',
+ created_at: '2013-10-03T12:50:33.000+05:30',
+ parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
+ message:
+ 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n\nchangelog updated to include ssh key retrieval feature update\n',
+ author_name: 'devaroop',
+ author_email: 'devaroop123@yahoo.co.in',
+ authored_date: '2013-10-02T20:39:29.000+05:30',
+ committer_name: 'devaroop',
+ committer_email: 'devaroop123@yahoo.co.in',
+ committed_date: '2013-10-03T12:50:33.000+05:30',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+ commit_path: '/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/pipelines/132/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/pipelines/132/cancel',
+ created_at: '2017-05-24T14:46:24.644Z',
+ updated_at: '2017-05-24T14:48:55.226Z',
+ },
+ {
+ id: 134,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/pipelines/133',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/pipelines/133',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: 'crowd',
+ path: '/gitlab-org/gitlab-foss/commits/crowd',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+ short_id: 'b6bd4856',
+ title: 'getting user keys publically through http without any authentication, the github…',
+ created_at: '2013-10-02T20:39:29.000+05:30',
+ parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'],
+ message:
+ 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n',
+ author_name: 'devaroop',
+ author_email: 'devaroop123@yahoo.co.in',
+ authored_date: '2013-10-02T20:39:29.000+05:30',
+ committer_name: 'devaroop',
+ committer_email: 'devaroop123@yahoo.co.in',
+ committed_date: '2013-10-02T20:39:29.000+05:30',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+ commit_path: '/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/pipelines/133/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/pipelines/133/cancel',
+ created_at: '2017-05-24T14:46:24.648Z',
+ updated_at: '2017-05-24T14:48:59.673Z',
+ },
+ {
+ id: 135,
+ active: true,
+ path: '/gitlab-org/gitlab-foss/pipelines/130',
+ project: {
+ name: 'GitLabCE',
+ },
+ details: {
+ status: {
+ icon: 'status_running',
+ text: 'running',
+ label: 'running',
+ group: 'running',
+ has_details: true,
+ details_path: '/gitlab-org/gitlab-foss/pipelines/130',
+ favicon:
+ '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
+ },
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: true,
+ },
+ ref: {
+ name: 'crowd',
+ path: '/gitlab-org/gitlab-foss/commits/crowd',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: '6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+ short_id: '6d7ced4a',
+ title: 'Whitespace fixes to patch',
+ created_at: '2013-10-08T13:53:22.000-05:00',
+ parent_ids: ['1875141a963a4238bda29011d8f7105839485253'],
+ message: 'Whitespace fixes to patch\n',
+ author_name: 'Dale Hamel',
+ author_email: 'dale.hamel@srvthe.net',
+ authored_date: '2013-10-08T13:53:22.000-05:00',
+ committer_name: 'Dale Hamel',
+ committer_email: 'dale.hamel@invenia.ca',
+ committed_date: '2013-10-08T13:53:22.000-05:00',
+ author_gravatar_url:
+ 'http://www.gravatar.com/avatar/cd08930e69fa5ad1a669206e7bafe476?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+ commit_path: '/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f',
+ },
+ retry_path: '/gitlab-org/gitlab-foss/pipelines/130/retry',
+ cancel_path: '/gitlab-org/gitlab-foss/pipelines/130/cancel',
+ created_at: '2017-05-24T14:46:24.630Z',
+ updated_at: '2017-05-24T14:49:45.091Z',
+ },
+ ],
+};
diff --git a/spec/javascripts/pipelines/linked_pipelines_mock.json b/spec/javascripts/pipelines/linked_pipelines_mock.json
new file mode 100644
index 00000000000..b498903f804
--- /dev/null
+++ b/spec/javascripts/pipelines/linked_pipelines_mock.json
@@ -0,0 +1,3532 @@
+{
+ "id": 23211253,
+ "user": {
+ "id": 3585,
+ "name": "Achilleas Pipinellis",
+ "username": "axil",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png",
+ "web_url": "https://gitlab.com/axil",
+ "status_tooltip_html": "\u003cspan class=\"user-status-emoji has-tooltip\" title=\"I like pizza\" data-html=\"true\" data-placement=\"top\"\u003e\u003cgl-emoji title=\"slice of pizza\" data-name=\"pizza\" data-unicode-version=\"6.0\"\u003e🍕\u003c/gl-emoji\u003e\u003c/span\u003e",
+ "path": "/axil"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "push",
+ "created_at": "2018-06-05T11:31:30.452Z",
+ "updated_at": "2018-10-31T16:35:31.305Z",
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253",
+ "flags": {
+ "latest": false,
+ "stuck": false,
+ "auto_devops": false,
+ "merge_request": 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/gitlab-runner/pipelines/23211253",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "duration": 53,
+ "finished_at": "2018-10-31T16:35:31.299Z",
+ "stages": [
+ {
+ "name": "prebuild",
+ "title": "prebuild: passed",
+ "groups": [
+ {
+ "name": "review-docs-deploy",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "manual play action",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 72469032,
+ "name": "review-docs-deploy",
+ "started": "2018-10-31T16:34:58.778Z",
+ "archived": false,
+ "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469032",
+ "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/retry",
+ "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-06-05T11:31:30.495Z",
+ "updated_at": "2018-10-31T16:35:31.251Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "manual play action",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild",
+ "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=prebuild"
+ },
+ {
+ "name": "test",
+ "title": "test: passed",
+ "groups": [
+ {
+ "name": "docs check links",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033",
+ "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/gitlab-runner/-/jobs/72469033/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 72469033,
+ "name": "docs check links",
+ "started": "2018-06-05T11:31:33.240Z",
+ "archived": false,
+ "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469033",
+ "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-06-05T11:31:30.627Z",
+ "updated_at": "2018-06-05T11:31:54.363Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033",
+ "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/gitlab-runner/-/jobs/72469033/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/gitlab-runner/pipelines/23211253#test",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253#test",
+ "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=test"
+ },
+ {
+ "name": "cleanup",
+ "title": "cleanup: skipped",
+ "groups": [
+ {
+ "name": "review-docs-cleanup",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual stop action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "stop",
+ "title": "Stop",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "method": "post",
+ "button_title": "Stop this environment"
+ }
+ },
+ "jobs": [
+ {
+ "id": 72469034,
+ "name": "review-docs-cleanup",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469034",
+ "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-06-05T11:31:30.760Z",
+ "updated_at": "2018-06-05T11:31:56.037Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual stop action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "stop",
+ "title": "Stop",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "method": "post",
+ "button_title": "Stop this environment"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup",
+ "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=cleanup"
+ }
+ ],
+ "artifacts": [],
+ "manual_actions": [
+ {
+ "name": "review-docs-cleanup",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "review-docs-deploy",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "playable": true,
+ "scheduled": false
+ }
+ ],
+ "scheduled_actions": []
+ },
+ "ref": {
+ "name": "docs/add-development-guide-to-readme",
+ "path": "/gitlab-org/gitlab-runner/commits/docs/add-development-guide-to-readme",
+ "tag": false,
+ "branch": true,
+ "merge_request": false
+ },
+ "commit": {
+ "id": "8083eb0a920572214d0dccedd7981f05d535ad46",
+ "short_id": "8083eb0a",
+ "title": "Add link to development guide in readme",
+ "created_at": "2018-06-05T11:30:48.000Z",
+ "parent_ids": ["1d7cf79b5a1a2121b9474ac20d61c1b8f621289d"],
+ "message": "Add link to development guide in readme\n\nCloses https://gitlab.com/gitlab-org/gitlab-runner/issues/3122\n",
+ "author_name": "Achilleas Pipinellis",
+ "author_email": "axil@gitlab.com",
+ "authored_date": "2018-06-05T11:30:48.000Z",
+ "committer_name": "Achilleas Pipinellis",
+ "committer_email": "axil@gitlab.com",
+ "committed_date": "2018-06-05T11:30:48.000Z",
+ "author": {
+ "id": 3585,
+ "name": "Achilleas Pipinellis",
+ "username": "axil",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png",
+ "web_url": "https://gitlab.com/axil",
+ "status_tooltip_html": null,
+ "path": "/axil"
+ },
+ "author_gravatar_url": "https://secure.gravatar.com/avatar/1d37af00eec153a8333a4ce18e9aea41?s=80\u0026d=identicon",
+ "commit_url": "https://gitlab.com/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46",
+ "commit_path": "/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46"
+ },
+ "triggered_by": {
+ "id": 12,
+ "user": {
+ "id": 376774,
+ "name": "Alessio Caiazza",
+ "username": "nolith",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png",
+ "web_url": "https://gitlab.com/nolith",
+ "status_tooltip_html": null,
+ "path": "/nolith"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "pipeline",
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "details": {
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ },
+ "duration": 118,
+ "finished_at": "2018-10-31T16:41:40.615Z",
+ "stages": [
+ {
+ "name": "build-images",
+ "title": "build-images: skipped",
+ "groups": [
+ {
+ "name": "image:bootstrap",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 11421321982853,
+ "name": "image:bootstrap",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982853",
+ "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.704Z",
+ "updated_at": "2018-10-31T16:35:24.118Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "image:builder-onbuild",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 1149822131854,
+ "name": "image:builder-onbuild",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982854",
+ "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.728Z",
+ "updated_at": "2018-10-31T16:35:24.070Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "image:nginx-onbuild",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 11498285523424,
+ "name": "image:nginx-onbuild",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982855",
+ "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.753Z",
+ "updated_at": "2018-10-31T16:35:24.033Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images",
+ "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build-images"
+ },
+ {
+ "name": "build",
+ "title": "build: failed",
+ "groups": [
+ {
+ "name": "compile_dev",
+ "size": 1,
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed - (script failure)",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694",
+ "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_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/528/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 1149846949786,
+ "name": "compile_dev",
+ "started": "2018-10-31T16:39:41.598Z",
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114984694",
+ "retry_path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:39:41.138Z",
+ "updated_at": "2018-10-31T16:41:40.072Z",
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed - (script failure)",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694",
+ "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_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/528/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "recoverable": false
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ },
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build",
+ "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build"
+ },
+ {
+ "name": "deploy",
+ "title": "deploy: skipped",
+ "groups": [
+ {
+ "name": "review",
+ "size": 1,
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "jobs": [
+ {
+ "id": 11498282342357,
+ "name": "review",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982857",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.805Z",
+ "updated_at": "2018-10-31T16:41:40.569Z",
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ }
+ }
+ ]
+ },
+ {
+ "name": "review_stop",
+ "size": 1,
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "jobs": [
+ {
+ "id": 114982858,
+ "name": "review_stop",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982858",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.840Z",
+ "updated_at": "2018-10-31T16:41:40.480Z",
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy",
+ "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=deploy"
+ }
+ ],
+ "artifacts": [],
+ "manual_actions": [
+ {
+ "name": "image:bootstrap",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "image:builder-onbuild",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "image:nginx-onbuild",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "review_stop",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982858/play",
+ "playable": false,
+ "scheduled": false
+ }
+ ],
+ "scheduled_actions": []
+ },
+ "project": {
+ "id": 1794617,
+ "name": "Test",
+ "full_path": "/gitlab-com/gitlab-docs",
+ "full_name": "GitLab.com / GitLab Docs"
+ },
+ "triggered_by": {
+ "id": 349932310342451,
+ "user": {
+ "id": 376774,
+ "name": "Alessio Caiazza",
+ "username": "nolith",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png",
+ "web_url": "https://gitlab.com/nolith",
+ "status_tooltip_html": null,
+ "path": "/nolith"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "pipeline",
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "details": {
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ },
+ "duration": 118,
+ "finished_at": "2018-10-31T16:41:40.615Z",
+ "stages": [
+ {
+ "name": "build-images",
+ "title": "build-images: skipped",
+ "groups": [
+ {
+ "name": "image:bootstrap",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 11421321982853,
+ "name": "image:bootstrap",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982853",
+ "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.704Z",
+ "updated_at": "2018-10-31T16:35:24.118Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "image:builder-onbuild",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 1149822131854,
+ "name": "image:builder-onbuild",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982854",
+ "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.728Z",
+ "updated_at": "2018-10-31T16:35:24.070Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "image:nginx-onbuild",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 11498285523424,
+ "name": "image:nginx-onbuild",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982855",
+ "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.753Z",
+ "updated_at": "2018-10-31T16:35:24.033Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images",
+ "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build-images"
+ },
+ {
+ "name": "build",
+ "title": "build: failed",
+ "groups": [
+ {
+ "name": "compile_dev",
+ "size": 1,
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed - (script failure)",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694",
+ "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_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 1149846949786,
+ "name": "compile_dev",
+ "started": "2018-10-31T16:39:41.598Z",
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114984694",
+ "retry_path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:39:41.138Z",
+ "updated_at": "2018-10-31T16:41:40.072Z",
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed - (script failure)",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694",
+ "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_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "recoverable": false
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ },
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build",
+ "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build"
+ },
+ {
+ "name": "deploy",
+ "title": "deploy: skipped",
+ "groups": [
+ {
+ "name": "review",
+ "size": 1,
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "jobs": [
+ {
+ "id": 11498282342357,
+ "name": "review",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982857",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.805Z",
+ "updated_at": "2018-10-31T16:41:40.569Z",
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ }
+ }
+ ]
+ },
+ {
+ "name": "review_stop",
+ "size": 1,
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "jobs": [
+ {
+ "id": 114982858,
+ "name": "review_stop",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982858",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.840Z",
+ "updated_at": "2018-10-31T16:41:40.480Z",
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy",
+ "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=deploy"
+ }
+ ],
+ "artifacts": [],
+ "manual_actions": [
+ {
+ "name": "image:bootstrap",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "image:builder-onbuild",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "image:nginx-onbuild",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "review_stop",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982858/play",
+ "playable": false,
+ "scheduled": false
+ }
+ ],
+ "scheduled_actions": []
+ },
+ "project": {
+ "id": 1794617,
+ "name": "GitLab Docs",
+ "full_path": "/gitlab-com/gitlab-docs",
+ "full_name": "GitLab.com / GitLab Docs"
+ }
+ },
+ "triggered": []
+ },
+ "triggered": [
+ {
+ "id": 34993051,
+ "user": {
+ "id": 376774,
+ "name": "Alessio Caiazza",
+ "username": "nolith",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png",
+ "web_url": "https://gitlab.com/nolith",
+ "status_tooltip_html": null,
+ "path": "/nolith"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "pipeline",
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "details": {
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ },
+ "duration": 118,
+ "finished_at": "2018-10-31T16:41:40.615Z",
+ "stages": [
+ {
+ "name": "build-images",
+ "title": "build-images: skipped",
+ "groups": [
+ {
+ "name": "image:bootstrap",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 114982853,
+ "name": "image:bootstrap",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982853",
+ "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.704Z",
+ "updated_at": "2018-10-31T16:35:24.118Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "image:builder-onbuild",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 114982854,
+ "name": "image:builder-onbuild",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982854",
+ "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.728Z",
+ "updated_at": "2018-10-31T16:35:24.070Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "image:nginx-onbuild",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 114982855,
+ "name": "image:nginx-onbuild",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982855",
+ "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.753Z",
+ "updated_at": "2018-10-31T16:35:24.033Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images",
+ "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build-images"
+ },
+ {
+ "name": "build",
+ "title": "build: failed",
+ "groups": [
+ {
+ "name": "compile_dev",
+ "size": 1,
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed - (script failure)",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694",
+ "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_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/528/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 114984694,
+ "name": "compile_dev",
+ "started": "2018-10-31T16:39:41.598Z",
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114984694",
+ "retry_path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:39:41.138Z",
+ "updated_at": "2018-10-31T16:41:40.072Z",
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed - (script failure)",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694",
+ "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_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/528/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "recoverable": false
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ },
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build",
+ "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build"
+ },
+ {
+ "name": "deploy",
+ "title": "deploy: skipped",
+ "groups": [
+ {
+ "name": "review",
+ "size": 1,
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "jobs": [
+ {
+ "id": 114982857,
+ "name": "review",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982857",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.805Z",
+ "updated_at": "2018-10-31T16:41:40.569Z",
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ }
+ }
+ ]
+ },
+ {
+ "name": "review_stop",
+ "size": 1,
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "jobs": [
+ {
+ "id": 114982858,
+ "name": "review_stop",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982858",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.840Z",
+ "updated_at": "2018-10-31T16:41:40.480Z",
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy",
+ "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=deploy"
+ }
+ ],
+ "artifacts": [],
+ "manual_actions": [
+ {
+ "name": "image:bootstrap",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "image:builder-onbuild",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "image:nginx-onbuild",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "review_stop",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982858/play",
+ "playable": false,
+ "scheduled": false
+ }
+ ],
+ "scheduled_actions": []
+ },
+ "project": {
+ "id": 1794617,
+ "name": "GitLab Docs",
+ "full_path": "/gitlab-com/gitlab-docs",
+ "full_name": "GitLab.com / GitLab Docs"
+ }
+ },
+ {
+ "id": 34993052,
+ "user": {
+ "id": 376774,
+ "name": "Alessio Caiazza",
+ "username": "nolith",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png",
+ "web_url": "https://gitlab.com/nolith",
+ "status_tooltip_html": null,
+ "path": "/nolith"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "pipeline",
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "details": {
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ },
+ "duration": 118,
+ "finished_at": "2018-10-31T16:41:40.615Z",
+ "stages": [
+ {
+ "name": "build-images",
+ "title": "build-images: skipped",
+ "groups": [
+ {
+ "name": "image:bootstrap",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 114982853,
+ "name": "image:bootstrap",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982853",
+ "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.704Z",
+ "updated_at": "2018-10-31T16:35:24.118Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982853",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "image:builder-onbuild",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 114982854,
+ "name": "image:builder-onbuild",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982854",
+ "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.728Z",
+ "updated_at": "2018-10-31T16:35:24.070Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982854",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "image:nginx-onbuild",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 1224982855,
+ "name": "image:nginx-onbuild",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982855",
+ "play_path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.753Z",
+ "updated_at": "2018-10-31T16:35:24.033Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual play action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982855",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build-images",
+ "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build-images"
+ },
+ {
+ "name": "build",
+ "title": "build: failed",
+ "groups": [
+ {
+ "name": "compile_dev",
+ "size": 1,
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed - (script failure)",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694",
+ "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_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 1123984694,
+ "name": "compile_dev",
+ "started": "2018-10-31T16:39:41.598Z",
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114984694",
+ "retry_path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:39:41.138Z",
+ "updated_at": "2018-10-31T16:41:40.072Z",
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed - (script failure)",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114984694",
+ "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_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114984694/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "recoverable": false
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#build",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ },
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051#build",
+ "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=build"
+ },
+ {
+ "name": "deploy",
+ "title": "deploy: skipped",
+ "groups": [
+ {
+ "name": "review",
+ "size": 1,
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "jobs": [
+ {
+ "id": 1143232982857,
+ "name": "review",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982857",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.805Z",
+ "updated_at": "2018-10-31T16:41:40.569Z",
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982857",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ }
+ }
+ ]
+ },
+ {
+ "name": "review_stop",
+ "size": 1,
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "jobs": [
+ {
+ "id": 114921313182858,
+ "name": "review_stop",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-com/gitlab-docs/-/jobs/114982858",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-10-31T16:35:23.840Z",
+ "updated_at": "2018-10-31T16:41:40.480Z",
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/-/jobs/114982858",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051#deploy",
+ "dropdown_path": "/gitlab-com/gitlab-docs/pipelines/34993051/stage.json?stage=deploy"
+ }
+ ],
+ "artifacts": [],
+ "manual_actions": [
+ {
+ "name": "image:bootstrap",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982853/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "image:builder-onbuild",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982854/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "image:nginx-onbuild",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982855/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "review_stop",
+ "path": "/gitlab-com/gitlab-docs/-/jobs/114982858/play",
+ "playable": false,
+ "scheduled": false
+ }
+ ],
+ "scheduled_actions": []
+ },
+ "project": {
+ "id": 1794617,
+ "name": "GitLab Docs",
+ "full_path": "/gitlab-com/gitlab-docs",
+ "full_name": "GitLab.com / GitLab Docs"
+ },
+ "triggered": [
+ {
+ "id": 26,
+ "user": null,
+ "active": false,
+ "coverage": null,
+ "source": "push",
+ "created_at": "2019-01-06T17:48:37.599Z",
+ "updated_at": "2019-01-06T17:48:38.371Z",
+ "path": "/h5bp/html5-boilerplate/pipelines/26",
+ "flags": {
+ "latest": true,
+ "stuck": false,
+ "auto_devops": false,
+ "merge_request": false,
+ "yaml_errors": false,
+ "retryable": true,
+ "cancelable": false,
+ "failure_reason": false
+ },
+ "details": {
+ "status": {
+ "icon": "status_warning",
+ "text": "passed",
+ "label": "passed with warnings",
+ "group": "success-with-warnings",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/pipelines/26",
+ "illustration": null,
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "duration": null,
+ "finished_at": "2019-01-06T17:48:38.370Z",
+ "stages": [
+ {
+ "name": "build",
+ "title": "build: passed",
+ "groups": [
+ {
+ "name": "build:linux",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/526",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/526/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 526,
+ "name": "build:linux",
+ "started": "2019-01-06T08:48:20.236Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/526",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/526/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:37.806Z",
+ "updated_at": "2019-01-06T17:48:37.806Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/526",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/526/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "build:osx",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/527",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/527/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 527,
+ "name": "build:osx",
+ "started": "2019-01-06T07:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/527",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/527/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:37.846Z",
+ "updated_at": "2019-01-06T17:48:37.846Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/527",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/527/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": "/h5bp/html5-boilerplate/pipelines/26#build",
+ "illustration": null,
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/h5bp/html5-boilerplate/pipelines/26#build",
+ "dropdown_path": "/h5bp/html5-boilerplate/pipelines/26/stage.json?stage=build"
+ },
+ {
+ "name": "test",
+ "title": "test: passed with warnings",
+ "groups": [
+ {
+ "name": "jenkins",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": null,
+ "group": "success",
+ "tooltip": null,
+ "has_details": false,
+ "details_path": null,
+ "illustration": null,
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "jobs": [
+ {
+ "id": 546,
+ "name": "jenkins",
+ "started": "2019-01-06T11:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/546",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:38.359Z",
+ "updated_at": "2019-01-06T17:48:38.359Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": null,
+ "group": "success",
+ "tooltip": null,
+ "has_details": false,
+ "details_path": null,
+ "illustration": null,
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ }
+ }
+ ]
+ },
+ {
+ "name": "rspec:linux",
+ "size": 3,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": false,
+ "details_path": null,
+ "illustration": null,
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "jobs": [
+ {
+ "id": 528,
+ "name": "rspec:linux 0 3",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/528",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/528/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:37.885Z",
+ "updated_at": "2019-01-06T17:48:37.885Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/528",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/528/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ },
+ {
+ "id": 529,
+ "name": "rspec:linux 1 3",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/529",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/529/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:37.907Z",
+ "updated_at": "2019-01-06T17:48:37.907Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/529",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/529/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ },
+ {
+ "id": 530,
+ "name": "rspec:linux 2 3",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/530",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/530/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:37.927Z",
+ "updated_at": "2019-01-06T17:48:37.927Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/530",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/530/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "rspec:osx",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/535",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/535/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 535,
+ "name": "rspec:osx",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/535",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/535/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:38.018Z",
+ "updated_at": "2019-01-06T17:48:38.018Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/535",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/535/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "rspec:windows",
+ "size": 3,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": false,
+ "details_path": null,
+ "illustration": null,
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "jobs": [
+ {
+ "id": 531,
+ "name": "rspec:windows 0 3",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/531",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/531/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:37.944Z",
+ "updated_at": "2019-01-06T17:48:37.944Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/531",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/531/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ },
+ {
+ "id": 532,
+ "name": "rspec:windows 1 3",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/532",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/532/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:37.962Z",
+ "updated_at": "2019-01-06T17:48:37.962Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/532",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/532/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ },
+ {
+ "id": 534,
+ "name": "rspec:windows 2 3",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/534",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/534/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:37.999Z",
+ "updated_at": "2019-01-06T17:48:37.999Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/534",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/534/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "spinach:linux",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/536",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/536/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 536,
+ "name": "spinach:linux",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/536",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/536/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:38.050Z",
+ "updated_at": "2019-01-06T17:48:38.050Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/536",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/536/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "spinach:osx",
+ "size": 1,
+ "status": {
+ "icon": "status_warning",
+ "text": "failed",
+ "label": "failed (allowed to fail)",
+ "group": "failed-with-warnings",
+ "tooltip": "failed - (unknown failure) (allowed to fail)",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/537",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/537/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 537,
+ "name": "spinach:osx",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/537",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/537/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:38.069Z",
+ "updated_at": "2019-01-06T17:48:38.069Z",
+ "status": {
+ "icon": "status_warning",
+ "text": "failed",
+ "label": "failed (allowed to fail)",
+ "group": "failed-with-warnings",
+ "tooltip": "failed - (unknown failure) (allowed to fail)",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/537",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/537/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "callout_message": "There is an unknown failure, please try again",
+ "recoverable": true
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_warning",
+ "text": "passed",
+ "label": "passed with warnings",
+ "group": "success-with-warnings",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/pipelines/26#test",
+ "illustration": null,
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/h5bp/html5-boilerplate/pipelines/26#test",
+ "dropdown_path": "/h5bp/html5-boilerplate/pipelines/26/stage.json?stage=test"
+ },
+ {
+ "name": "security",
+ "title": "security: passed",
+ "groups": [
+ {
+ "name": "container_scanning",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/541",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/541/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 541,
+ "name": "container_scanning",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/541",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/541/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:38.186Z",
+ "updated_at": "2019-01-06T17:48:38.186Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/541",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/541/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "dast",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/538",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/538/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 538,
+ "name": "dast",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/538",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/538/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:38.087Z",
+ "updated_at": "2019-01-06T17:48:38.087Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/538",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/538/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "dependency_scanning",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/540",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/540/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 540,
+ "name": "dependency_scanning",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/540",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/540/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:38.153Z",
+ "updated_at": "2019-01-06T17:48:38.153Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/540",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/540/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "sast",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/539",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/539/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 539,
+ "name": "sast",
+ "started": "2019-01-06T09:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/539",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/539/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:38.121Z",
+ "updated_at": "2019-01-06T17:48:38.121Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/539",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/539/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": "/h5bp/html5-boilerplate/pipelines/26#security",
+ "illustration": null,
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/h5bp/html5-boilerplate/pipelines/26#security",
+ "dropdown_path": "/h5bp/html5-boilerplate/pipelines/26/stage.json?stage=security"
+ },
+ {
+ "name": "deploy",
+ "title": "deploy: passed",
+ "groups": [
+ {
+ "name": "production",
+ "size": 1,
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/544",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "jobs": [
+ {
+ "id": 544,
+ "name": "production",
+ "started": null,
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/544",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:38.313Z",
+ "updated_at": "2019-01-06T17:48:38.313Z",
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/544",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ }
+ }
+ ]
+ },
+ {
+ "name": "staging",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/542",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/542/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 542,
+ "name": "staging",
+ "started": "2019-01-06T11:48:20.237Z",
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/542",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/542/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:38.219Z",
+ "updated_at": "2019-01-06T17:48:38.219Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/542",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job does not have a trace."
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "retry",
+ "title": "Retry",
+ "path": "/h5bp/html5-boilerplate/-/jobs/542/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "stop staging",
+ "size": 1,
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/543",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "jobs": [
+ {
+ "id": 543,
+ "name": "stop staging",
+ "started": null,
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/543",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:38.283Z",
+ "updated_at": "2019-01-06T17:48:38.283Z",
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/543",
+ "illustration": {
+ "image": "/assets/illustrations/skipped-job_empty-8b877955fbf175e42ae65b6cb95346e15282c6fc5b682756c329af3a0055225e.svg",
+ "size": "svg-430",
+ "title": "This job has been skipped"
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/pipelines/26#deploy",
+ "illustration": null,
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/h5bp/html5-boilerplate/pipelines/26#deploy",
+ "dropdown_path": "/h5bp/html5-boilerplate/pipelines/26/stage.json?stage=deploy"
+ },
+ {
+ "name": "notify",
+ "title": "notify: passed",
+ "groups": [
+ {
+ "name": "slack",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "manual play action",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/545",
+ "illustration": {
+ "image": "/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/h5bp/html5-boilerplate/-/jobs/545/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 545,
+ "name": "slack",
+ "started": null,
+ "archived": false,
+ "build_path": "/h5bp/html5-boilerplate/-/jobs/545",
+ "retry_path": "/h5bp/html5-boilerplate/-/jobs/545/retry",
+ "play_path": "/h5bp/html5-boilerplate/-/jobs/545/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2019-01-06T17:48:38.341Z",
+ "updated_at": "2019-01-06T17:48:38.341Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "manual play action",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/-/jobs/545",
+ "illustration": {
+ "image": "/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/h5bp/html5-boilerplate/-/jobs/545/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/h5bp/html5-boilerplate/pipelines/26#notify",
+ "illustration": null,
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/h5bp/html5-boilerplate/pipelines/26#notify",
+ "dropdown_path": "/h5bp/html5-boilerplate/pipelines/26/stage.json?stage=notify"
+ }
+ ],
+ "artifacts": [
+ {
+ "name": "build:linux",
+ "expired": null,
+ "expire_at": null,
+ "path": "/h5bp/html5-boilerplate/-/jobs/526/artifacts/download",
+ "browse_path": "/h5bp/html5-boilerplate/-/jobs/526/artifacts/browse"
+ },
+ {
+ "name": "build:osx",
+ "expired": null,
+ "expire_at": null,
+ "path": "/h5bp/html5-boilerplate/-/jobs/527/artifacts/download",
+ "browse_path": "/h5bp/html5-boilerplate/-/jobs/527/artifacts/browse"
+ }
+ ],
+ "manual_actions": [
+ {
+ "name": "stop staging",
+ "path": "/h5bp/html5-boilerplate/-/jobs/543/play",
+ "playable": false,
+ "scheduled": false
+ },
+ {
+ "name": "production",
+ "path": "/h5bp/html5-boilerplate/-/jobs/544/play",
+ "playable": false,
+ "scheduled": false
+ },
+ {
+ "name": "slack",
+ "path": "/h5bp/html5-boilerplate/-/jobs/545/play",
+ "playable": true,
+ "scheduled": false
+ }
+ ],
+ "scheduled_actions": []
+ },
+ "ref": {
+ "name": "master",
+ "path": "/h5bp/html5-boilerplate/commits/master",
+ "tag": false,
+ "branch": true,
+ "merge_request": false
+ },
+ "commit": {
+ "id": "bad98c453eab56d20057f3929989251d45cd1a8b",
+ "short_id": "bad98c45",
+ "title": "remove instances of shrink-to-fit=no (#2103)",
+ "created_at": "2018-12-17T20:52:18.000Z",
+ "parent_ids": ["49130f6cfe9ff1f749015d735649a2bc6f66cf3a"],
+ "message": "remove instances of shrink-to-fit=no (#2103)\n\ncloses #2102\r\n\r\nPer my findings, the need for it as a default was rectified with the release of iOS 9.3, where the viewport no longer shrunk to accommodate overflow, as was introduced in iOS 9.",
+ "author_name": "Scott O'Hara",
+ "author_email": "scottaohara@users.noreply.github.com",
+ "authored_date": "2018-12-17T20:52:18.000Z",
+ "committer_name": "Rob Larsen",
+ "committer_email": "rob@drunkenfist.com",
+ "committed_date": "2018-12-17T20:52:18.000Z",
+ "author": null,
+ "author_gravatar_url": "https://www.gravatar.com/avatar/6d597df7cf998d16cbe00ccac063b31e?s=80\u0026d=identicon",
+ "commit_url": "http://localhost:3001/h5bp/html5-boilerplate/commit/bad98c453eab56d20057f3929989251d45cd1a8b",
+ "commit_path": "/h5bp/html5-boilerplate/commit/bad98c453eab56d20057f3929989251d45cd1a8b"
+ },
+ "retry_path": "/h5bp/html5-boilerplate/pipelines/26/retry",
+ "triggered_by": {
+ "id": 4,
+ "user": null,
+ "active": false,
+ "coverage": null,
+ "source": "push",
+ "path": "/gitlab-org/gitlab-test/pipelines/4",
+ "details": {
+ "status": {
+ "icon": "status_warning",
+ "text": "passed",
+ "label": "passed with warnings",
+ "group": "success-with-warnings",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-test/pipelines/4",
+ "illustration": null,
+ "favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ }
+ },
+ "project": {
+ "id": 1,
+ "name": "Gitlab Test",
+ "full_path": "/gitlab-org/gitlab-test",
+ "full_name": "Gitlab Org / Gitlab Test"
+ }
+ },
+ "triggered": [],
+ "project": {
+ "id": 1794617,
+ "name": "GitLab Docs",
+ "full_path": "/gitlab-com/gitlab-docs",
+ "full_name": "GitLab.com / GitLab Docs"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/javascripts/pipelines/stores/pipeline.json b/spec/javascripts/pipelines/stores/pipeline.json
new file mode 100644
index 00000000000..7d5891d3d52
--- /dev/null
+++ b/spec/javascripts/pipelines/stores/pipeline.json
@@ -0,0 +1,167 @@
+{
+ "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-foss#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": []
+}
diff --git a/spec/javascripts/pipelines/stores/pipeline_store.js b/spec/javascripts/pipelines/stores/pipeline_store.js
new file mode 100644
index 00000000000..4a0b3bf4c02
--- /dev/null
+++ b/spec/javascripts/pipelines/stores/pipeline_store.js
@@ -0,0 +1,165 @@
+import PipelineStore from '~/pipelines/stores/pipeline_store';
+import LinkedPipelines from '../linked_pipelines_mock.json';
+
+describe('EE Pipeline store', () => {
+ let store;
+ let data;
+
+ beforeEach(() => {
+ store = new PipelineStore();
+ data = Object.assign({}, LinkedPipelines);
+ });
+
+ describe('storePipeline', () => {
+ beforeAll(() => {
+ store.storePipeline(data);
+ });
+
+ describe('triggered_by', () => {
+ it('sets triggered_by as an array', () => {
+ expect(store.state.pipeline.triggered_by.length).toEqual(1);
+ });
+
+ it('adds isExpanding & isLoading keys set to false', () => {
+ expect(store.state.pipeline.triggered_by[0].isExpanded).toEqual(false);
+ expect(store.state.pipeline.triggered_by[0].isLoading).toEqual(false);
+ });
+
+ it('parses nested triggered_by', () => {
+ expect(store.state.pipeline.triggered_by[0].triggered_by.length).toEqual(1);
+ expect(store.state.pipeline.triggered_by[0].triggered_by[0].isExpanded).toEqual(false);
+ expect(store.state.pipeline.triggered_by[0].triggered_by[0].isLoading).toEqual(false);
+ });
+ });
+
+ describe('triggered', () => {
+ it('adds isExpanding & isLoading keys set to false for each triggered pipeline', () => {
+ store.state.pipeline.triggered.forEach(pipeline => {
+ expect(pipeline.isExpanded).toEqual(false);
+ expect(pipeline.isLoading).toEqual(false);
+ });
+ });
+
+ it('parses nested triggered pipelines', () => {
+ store.state.pipeline.triggered[1].triggered.forEach(pipeline => {
+ expect(pipeline.isExpanded).toEqual(false);
+ expect(pipeline.isLoading).toEqual(false);
+ });
+ });
+ });
+ });
+
+ describe('resetTriggeredByPipeline', () => {
+ beforeEach(() => {
+ store.storePipeline(data);
+ });
+
+ it('closes the pipeline & nested ones', () => {
+ store.state.pipeline.triggered_by[0].isExpanded = true;
+ store.state.pipeline.triggered_by[0].triggered_by[0].isExpanded = true;
+
+ store.resetTriggeredByPipeline(store.state.pipeline, store.state.pipeline.triggered_by[0]);
+
+ expect(store.state.pipeline.triggered_by[0].isExpanded).toEqual(false);
+ expect(store.state.pipeline.triggered_by[0].triggered_by[0].isExpanded).toEqual(false);
+ });
+ });
+
+ describe('openTriggeredByPipeline', () => {
+ beforeEach(() => {
+ store.storePipeline(data);
+ });
+
+ it('opens the given pipeline', () => {
+ store.openTriggeredByPipeline(store.state.pipeline, store.state.pipeline.triggered_by[0]);
+
+ expect(store.state.pipeline.triggered_by[0].isExpanded).toEqual(true);
+ });
+ });
+
+ describe('closeTriggeredByPipeline', () => {
+ beforeEach(() => {
+ store.storePipeline(data);
+ });
+
+ it('closes the given pipeline', () => {
+ // open it first
+ store.openTriggeredByPipeline(store.state.pipeline, store.state.pipeline.triggered_by[0]);
+
+ store.closeTriggeredByPipeline(store.state.pipeline, store.state.pipeline.triggered_by[0]);
+
+ expect(store.state.pipeline.triggered_by[0].isExpanded).toEqual(false);
+ });
+ });
+
+ describe('resetTriggeredPipelines', () => {
+ beforeEach(() => {
+ store.storePipeline(data);
+ });
+
+ it('closes the pipeline & nested ones', () => {
+ store.state.pipeline.triggered[0].isExpanded = true;
+ store.state.pipeline.triggered[0].triggered[0].isExpanded = true;
+
+ store.resetTriggeredPipeline(store.state.pipeline, store.state.pipeline.triggered[0]);
+
+ expect(store.state.pipeline.triggered[0].isExpanded).toEqual(false);
+ expect(store.state.pipeline.triggered[0].triggered[0].isExpanded).toEqual(false);
+ });
+ });
+
+ describe('openTriggeredPipeline', () => {
+ beforeEach(() => {
+ store.storePipeline(data);
+ });
+
+ it('opens the given pipeline', () => {
+ store.openTriggeredPipeline(store.state.pipeline, store.state.pipeline.triggered[0]);
+
+ expect(store.state.pipeline.triggered[0].isExpanded).toEqual(true);
+ });
+ });
+
+ describe('closeTriggeredPipeline', () => {
+ beforeEach(() => {
+ store.storePipeline(data);
+ });
+
+ it('closes the given pipeline', () => {
+ // open it first
+ store.openTriggeredPipeline(store.state.pipeline, store.state.pipeline.triggered[0]);
+
+ store.closeTriggeredPipeline(store.state.pipeline, store.state.pipeline.triggered[0]);
+
+ expect(store.state.pipeline.triggered[0].isExpanded).toEqual(false);
+ });
+ });
+
+ describe('toggleLoading', () => {
+ beforeEach(() => {
+ store.storePipeline(data);
+ });
+
+ it('toggles the isLoading property for the given pipeline', () => {
+ store.togglePipeline(store.state.pipeline.triggered[0]);
+
+ expect(store.state.pipeline.triggered[0].isLoading).toEqual(true);
+ });
+ });
+
+ describe('addExpandedPipelineToRequestData', () => {
+ it('pushes the given id to expandedPipelines array', () => {
+ store.addExpandedPipelineToRequestData('213231');
+
+ expect(store.state.expandedPipelines).toEqual(['213231']);
+ });
+ });
+
+ describe('removeExpandedPipelineToRequestData', () => {
+ it('pushes the given id to expandedPipelines array', () => {
+ store.removeExpandedPipelineToRequestData('213231');
+
+ expect(store.state.expandedPipelines).toEqual([]);
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/stores/pipeline_with_triggered.json b/spec/javascripts/pipelines/stores/pipeline_with_triggered.json
new file mode 100644
index 00000000000..1fa15e45792
--- /dev/null
+++ b/spec/javascripts/pipelines/stores/pipeline_with_triggered.json
@@ -0,0 +1,381 @@
+{
+ "id": 23211253,
+ "user": {
+ "id": 3585,
+ "name": "Achilleas Pipinellis",
+ "username": "axil",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png",
+ "web_url": "https://gitlab.com/axil",
+ "status_tooltip_html": "\u003cspan class=\"user-status-emoji has-tooltip\" title=\"\" data-html=\"true\" data-placement=\"top\"\u003e\u003cgl-emoji title=\"trumpet\" data-name=\"trumpet\" data-unicode-version=\"6.0\"\u003e🎺\u003c/gl-emoji\u003e\u003c/span\u003e",
+ "path": "/axil"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "push",
+ "created_at": "2018-06-05T11:31:30.452Z",
+ "updated_at": "2018-10-31T16:35:31.305Z",
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253",
+ "flags": {
+ "latest": false,
+ "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/gitlab-runner/pipelines/23211253",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "duration": 53,
+ "finished_at": "2018-10-31T16:35:31.299Z",
+ "stages": [
+ {
+ "name": "prebuild",
+ "title": "prebuild: passed",
+ "groups": [
+ {
+ "name": "review-docs-deploy",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "manual play action",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 72469032,
+ "name": "review-docs-deploy",
+ "started": "2018-10-31T16:34:58.778Z",
+ "archived": false,
+ "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469032",
+ "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/retry",
+ "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-06-05T11:31:30.495Z",
+ "updated_at": "2018-10-31T16:35:31.251Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "manual play action",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild",
+ "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=prebuild"
+ },
+ {
+ "name": "test",
+ "title": "test: passed",
+ "groups": [
+ {
+ "name": "docs check links",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033",
+ "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/gitlab-runner/-/jobs/72469033/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 72469033,
+ "name": "docs check links",
+ "started": "2018-06-05T11:31:33.240Z",
+ "archived": false,
+ "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469033",
+ "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-06-05T11:31:30.627Z",
+ "updated_at": "2018-06-05T11:31:54.363Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033",
+ "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/gitlab-runner/-/jobs/72469033/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/gitlab-runner/pipelines/23211253#test",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253#test",
+ "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=test"
+ },
+ {
+ "name": "cleanup",
+ "title": "cleanup: skipped",
+ "groups": [
+ {
+ "name": "review-docs-cleanup",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual stop action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "stop",
+ "title": "Stop",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "method": "post",
+ "button_title": "Stop this environment"
+ }
+ },
+ "jobs": [
+ {
+ "id": 72469034,
+ "name": "review-docs-cleanup",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469034",
+ "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-06-05T11:31:30.760Z",
+ "updated_at": "2018-06-05T11:31:56.037Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual stop action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "stop",
+ "title": "Stop",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "method": "post",
+ "button_title": "Stop this environment"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup",
+ "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=cleanup"
+ }
+ ],
+ "artifacts": [],
+ "manual_actions": [
+ {
+ "name": "review-docs-cleanup",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "review-docs-deploy",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "playable": true,
+ "scheduled": false
+ }
+ ],
+ "scheduled_actions": []
+ },
+ "ref": {
+ "name": "docs/add-development-guide-to-readme",
+ "path": "/gitlab-org/gitlab-runner/commits/docs/add-development-guide-to-readme",
+ "tag": false,
+ "branch": true
+ },
+ "commit": {
+ "id": "8083eb0a920572214d0dccedd7981f05d535ad46",
+ "short_id": "8083eb0a",
+ "title": "Add link to development guide in readme",
+ "created_at": "2018-06-05T11:30:48.000Z",
+ "parent_ids": ["1d7cf79b5a1a2121b9474ac20d61c1b8f621289d"],
+ "message": "Add link to development guide in readme\n\nCloses https://gitlab.com/gitlab-org/gitlab-runner/issues/3122\n",
+ "author_name": "Achilleas Pipinellis",
+ "author_email": "axil@gitlab.com",
+ "authored_date": "2018-06-05T11:30:48.000Z",
+ "committer_name": "Achilleas Pipinellis",
+ "committer_email": "axil@gitlab.com",
+ "committed_date": "2018-06-05T11:30:48.000Z",
+ "author": {
+ "id": 3585,
+ "name": "Achilleas Pipinellis",
+ "username": "axil",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png",
+ "web_url": "https://gitlab.com/axil",
+ "status_tooltip_html": null,
+ "path": "/axil"
+ },
+ "author_gravatar_url": "https://secure.gravatar.com/avatar/1d37af00eec153a8333a4ce18e9aea41?s=80\u0026d=identicon",
+ "commit_url": "https://gitlab.com/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46",
+ "commit_path": "/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46"
+ },
+ "triggered_by": null,
+ "triggered": [
+ {
+ "id": 34993051,
+ "user": {
+ "id": 376774,
+ "name": "Alessio Caiazza",
+ "username": "nolith",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png",
+ "web_url": "https://gitlab.com/nolith",
+ "status_tooltip_html": null,
+ "path": "/nolith"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "pipeline",
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "details": {
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ }
+ },
+ "project": {
+ "id": 1794617,
+ "name": "GitLab Docs",
+ "full_path": "/gitlab-com/gitlab-docs",
+ "full_name": "GitLab.com / GitLab Docs"
+ }
+ }
+ ]
+}
diff --git a/spec/javascripts/pipelines/stores/pipeline_with_triggered_by.json b/spec/javascripts/pipelines/stores/pipeline_with_triggered_by.json
new file mode 100644
index 00000000000..7aeea6f3ebb
--- /dev/null
+++ b/spec/javascripts/pipelines/stores/pipeline_with_triggered_by.json
@@ -0,0 +1,379 @@
+{
+ "id": 23211253,
+ "user": {
+ "id": 3585,
+ "name": "Achilleas Pipinellis",
+ "username": "axil",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png",
+ "web_url": "https://gitlab.com/axil",
+ "status_tooltip_html": "\u003cspan class=\"user-status-emoji has-tooltip\" title=\"\" data-html=\"true\" data-placement=\"top\"\u003e\u003cgl-emoji title=\"trumpet\" data-name=\"trumpet\" data-unicode-version=\"6.0\"\u003e🎺\u003c/gl-emoji\u003e\u003c/span\u003e",
+ "path": "/axil"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "push",
+ "created_at": "2018-06-05T11:31:30.452Z",
+ "updated_at": "2018-10-31T16:35:31.305Z",
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253",
+ "flags": {
+ "latest": false,
+ "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/gitlab-runner/pipelines/23211253",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "duration": 53,
+ "finished_at": "2018-10-31T16:35:31.299Z",
+ "stages": [
+ {
+ "name": "prebuild",
+ "title": "prebuild: passed",
+ "groups": [
+ {
+ "name": "review-docs-deploy",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "manual play action",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 72469032,
+ "name": "review-docs-deploy",
+ "started": "2018-10-31T16:34:58.778Z",
+ "archived": false,
+ "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469032",
+ "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/retry",
+ "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-06-05T11:31:30.495Z",
+ "updated_at": "2018-10-31T16:35:31.251Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "manual play action",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild",
+ "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=prebuild"
+ },
+ {
+ "name": "test",
+ "title": "test: passed",
+ "groups": [
+ {
+ "name": "docs check links",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033",
+ "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/gitlab-runner/-/jobs/72469033/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 72469033,
+ "name": "docs check links",
+ "started": "2018-06-05T11:31:33.240Z",
+ "archived": false,
+ "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469033",
+ "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-06-05T11:31:30.627Z",
+ "updated_at": "2018-06-05T11:31:54.363Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033",
+ "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/gitlab-runner/-/jobs/72469033/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/gitlab-runner/pipelines/23211253#test",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253#test",
+ "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=test"
+ },
+ {
+ "name": "cleanup",
+ "title": "cleanup: skipped",
+ "groups": [
+ {
+ "name": "review-docs-cleanup",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual stop action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "stop",
+ "title": "Stop",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "method": "post",
+ "button_title": "Stop this environment"
+ }
+ },
+ "jobs": [
+ {
+ "id": 72469034,
+ "name": "review-docs-cleanup",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469034",
+ "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-06-05T11:31:30.760Z",
+ "updated_at": "2018-06-05T11:31:56.037Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual stop action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "stop",
+ "title": "Stop",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "method": "post",
+ "button_title": "Stop this environment"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup",
+ "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=cleanup"
+ }
+ ],
+ "artifacts": [],
+ "manual_actions": [
+ {
+ "name": "review-docs-cleanup",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "review-docs-deploy",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "playable": true,
+ "scheduled": false
+ }
+ ],
+ "scheduled_actions": []
+ },
+ "ref": {
+ "name": "docs/add-development-guide-to-readme",
+ "path": "/gitlab-org/gitlab-runner/commits/docs/add-development-guide-to-readme",
+ "tag": false,
+ "branch": true
+ },
+ "commit": {
+ "id": "8083eb0a920572214d0dccedd7981f05d535ad46",
+ "short_id": "8083eb0a",
+ "title": "Add link to development guide in readme",
+ "created_at": "2018-06-05T11:30:48.000Z",
+ "parent_ids": ["1d7cf79b5a1a2121b9474ac20d61c1b8f621289d"],
+ "message": "Add link to development guide in readme\n\nCloses https://gitlab.com/gitlab-org/gitlab-runner/issues/3122\n",
+ "author_name": "Achilleas Pipinellis",
+ "author_email": "axil@gitlab.com",
+ "authored_date": "2018-06-05T11:30:48.000Z",
+ "committer_name": "Achilleas Pipinellis",
+ "committer_email": "axil@gitlab.com",
+ "committed_date": "2018-06-05T11:30:48.000Z",
+ "author": {
+ "id": 3585,
+ "name": "Achilleas Pipinellis",
+ "username": "axil",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png",
+ "web_url": "https://gitlab.com/axil",
+ "status_tooltip_html": null,
+ "path": "/axil"
+ },
+ "author_gravatar_url": "https://secure.gravatar.com/avatar/1d37af00eec153a8333a4ce18e9aea41?s=80\u0026d=identicon",
+ "commit_url": "https://gitlab.com/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46",
+ "commit_path": "/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46"
+ },
+ "triggered_by": {
+ "id": 34993051,
+ "user": {
+ "id": 376774,
+ "name": "Alessio Caiazza",
+ "username": "nolith",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png",
+ "web_url": "https://gitlab.com/nolith",
+ "status_tooltip_html": null,
+ "path": "/nolith"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "pipeline",
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "details": {
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ }
+ },
+ "project": {
+ "id": 1794617,
+ "name": "GitLab Docs",
+ "full_path": "/gitlab-com/gitlab-docs",
+ "full_name": "GitLab.com / GitLab Docs"
+ }
+ },
+ "triggered": []
+}
diff --git a/spec/javascripts/pipelines/stores/pipeline_with_triggered_triggered_by.json b/spec/javascripts/pipelines/stores/pipeline_with_triggered_triggered_by.json
new file mode 100644
index 00000000000..2402cbae6c8
--- /dev/null
+++ b/spec/javascripts/pipelines/stores/pipeline_with_triggered_triggered_by.json
@@ -0,0 +1,452 @@
+{
+ "id": 23211253,
+ "user": {
+ "id": 3585,
+ "name": "Achilleas Pipinellis",
+ "username": "axil",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png",
+ "web_url": "https://gitlab.com/axil",
+ "status_tooltip_html": "\u003cspan class=\"user-status-emoji has-tooltip\" title=\"\" data-html=\"true\" data-placement=\"top\"\u003e\u003cgl-emoji title=\"trumpet\" data-name=\"trumpet\" data-unicode-version=\"6.0\"\u003e🎺\u003c/gl-emoji\u003e\u003c/span\u003e",
+ "path": "/axil"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "push",
+ "created_at": "2018-06-05T11:31:30.452Z",
+ "updated_at": "2018-10-31T16:35:31.305Z",
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253",
+ "flags": {
+ "latest": false,
+ "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/gitlab-runner/pipelines/23211253",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "duration": 53,
+ "finished_at": "2018-10-31T16:35:31.299Z",
+ "stages": [
+ {
+ "name": "prebuild",
+ "title": "prebuild: passed",
+ "groups": [
+ {
+ "name": "review-docs-deploy",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "manual play action",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ },
+ "jobs": [
+ {
+ "id": 72469032,
+ "name": "review-docs-deploy",
+ "started": "2018-10-31T16:34:58.778Z",
+ "archived": false,
+ "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469032",
+ "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/retry",
+ "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-06-05T11:31:30.495Z",
+ "updated_at": "2018-10-31T16:35:31.251Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "manual play action",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469032",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png",
+ "action": {
+ "icon": "play",
+ "title": "Play",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "method": "post",
+ "button_title": "Trigger this manual action"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253#prebuild",
+ "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=prebuild"
+ },
+ {
+ "name": "test",
+ "title": "test: passed",
+ "groups": [
+ {
+ "name": "docs check links",
+ "size": 1,
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033",
+ "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/gitlab-runner/-/jobs/72469033/retry",
+ "method": "post",
+ "button_title": "Retry this job"
+ }
+ },
+ "jobs": [
+ {
+ "id": 72469033,
+ "name": "docs check links",
+ "started": "2018-06-05T11:31:33.240Z",
+ "archived": false,
+ "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469033",
+ "retry_path": "/gitlab-org/gitlab-runner/-/jobs/72469033/retry",
+ "playable": false,
+ "scheduled": false,
+ "created_at": "2018-06-05T11:31:30.627Z",
+ "updated_at": "2018-06-05T11:31:54.363Z",
+ "status": {
+ "icon": "status_success",
+ "text": "passed",
+ "label": "passed",
+ "group": "success",
+ "tooltip": "passed",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469033",
+ "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/gitlab-runner/-/jobs/72469033/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/gitlab-runner/pipelines/23211253#test",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
+ },
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253#test",
+ "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=test"
+ },
+ {
+ "name": "cleanup",
+ "title": "cleanup: skipped",
+ "groups": [
+ {
+ "name": "review-docs-cleanup",
+ "size": 1,
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual stop action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "stop",
+ "title": "Stop",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "method": "post",
+ "button_title": "Stop this environment"
+ }
+ },
+ "jobs": [
+ {
+ "id": 72469034,
+ "name": "review-docs-cleanup",
+ "started": null,
+ "archived": false,
+ "build_path": "/gitlab-org/gitlab-runner/-/jobs/72469034",
+ "play_path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "playable": true,
+ "scheduled": false,
+ "created_at": "2018-06-05T11:31:30.760Z",
+ "updated_at": "2018-06-05T11:31:56.037Z",
+ "status": {
+ "icon": "status_manual",
+ "text": "manual",
+ "label": "manual stop action",
+ "group": "manual",
+ "tooltip": "manual action",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/-/jobs/72469034",
+ "illustration": {
+ "image": "https://assets.gitlab-static.net/assets/illustrations/manual_action-2b4ca0d1bcfd92aebf33d484e36cbf7a102d007f76b5a0cfea636033a629d601.svg",
+ "size": "svg-394",
+ "title": "This job requires a manual action",
+ "content": "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
+ },
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png",
+ "action": {
+ "icon": "stop",
+ "title": "Stop",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "method": "post",
+ "button_title": "Stop this environment"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "status": {
+ "icon": "status_skipped",
+ "text": "skipped",
+ "label": "skipped",
+ "group": "skipped",
+ "tooltip": "skipped",
+ "has_details": true,
+ "details_path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_skipped-0b9c5e543588945e8c4ca57786bbf2d0c56631959c9f853300392d0315be829b.png"
+ },
+ "path": "/gitlab-org/gitlab-runner/pipelines/23211253#cleanup",
+ "dropdown_path": "/gitlab-org/gitlab-runner/pipelines/23211253/stage.json?stage=cleanup"
+ }
+ ],
+ "artifacts": [],
+ "manual_actions": [
+ {
+ "name": "review-docs-cleanup",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469034/play",
+ "playable": true,
+ "scheduled": false
+ },
+ {
+ "name": "review-docs-deploy",
+ "path": "/gitlab-org/gitlab-runner/-/jobs/72469032/play",
+ "playable": true,
+ "scheduled": false
+ }
+ ],
+ "scheduled_actions": []
+ },
+ "ref": {
+ "name": "docs/add-development-guide-to-readme",
+ "path": "/gitlab-org/gitlab-runner/commits/docs/add-development-guide-to-readme",
+ "tag": false,
+ "branch": true
+ },
+ "commit": {
+ "id": "8083eb0a920572214d0dccedd7981f05d535ad46",
+ "short_id": "8083eb0a",
+ "title": "Add link to development guide in readme",
+ "created_at": "2018-06-05T11:30:48.000Z",
+ "parent_ids": ["1d7cf79b5a1a2121b9474ac20d61c1b8f621289d"],
+ "message": "Add link to development guide in readme\n\nCloses https://gitlab.com/gitlab-org/gitlab-runner/issues/3122\n",
+ "author_name": "Achilleas Pipinellis",
+ "author_email": "axil@gitlab.com",
+ "authored_date": "2018-06-05T11:30:48.000Z",
+ "committer_name": "Achilleas Pipinellis",
+ "committer_email": "axil@gitlab.com",
+ "committed_date": "2018-06-05T11:30:48.000Z",
+ "author": {
+ "id": 3585,
+ "name": "Achilleas Pipinellis",
+ "username": "axil",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/3585/avatar.png",
+ "web_url": "https://gitlab.com/axil",
+ "status_tooltip_html": null,
+ "path": "/axil"
+ },
+ "author_gravatar_url": "https://secure.gravatar.com/avatar/1d37af00eec153a8333a4ce18e9aea41?s=80\u0026d=identicon",
+ "commit_url": "https://gitlab.com/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46",
+ "commit_path": "/gitlab-org/gitlab-runner/commit/8083eb0a920572214d0dccedd7981f05d535ad46"
+ },
+ "triggered_by": {
+ "id": 34993051,
+ "user": {
+ "id": 376774,
+ "name": "Alessio Caiazza",
+ "username": "nolith",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png",
+ "web_url": "https://gitlab.com/nolith",
+ "status_tooltip_html": null,
+ "path": "/nolith"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "pipeline",
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "details": {
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ }
+ },
+ "project": {
+ "id": 1794617,
+ "name": "GitLab Docs",
+ "full_path": "/gitlab-com/gitlab-docs",
+ "full_name": "GitLab.com / GitLab Docs"
+ }
+ },
+ "triggered": [
+ {
+ "id": 349233051,
+ "user": {
+ "id": 376774,
+ "name": "Alessio Caiazza",
+ "username": "nolith",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png",
+ "web_url": "https://gitlab.com/nolith",
+ "status_tooltip_html": null,
+ "path": "/nolith"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "pipeline",
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "details": {
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/349233051",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ }
+ },
+ "project": {
+ "id": 1794617,
+ "name": "GitLab Docs",
+ "full_path": "/gitlab-com/gitlab-docs",
+ "full_name": "GitLab.com / GitLab Docs"
+ }
+ },
+ {
+ "id": 34993023,
+ "user": {
+ "id": 376774,
+ "name": "Alessio Caiazza",
+ "username": "nolith",
+ "state": "active",
+ "avatar_url": "https://assets.gitlab-static.net/uploads/-/system/user/avatar/376774/avatar.png",
+ "web_url": "https://gitlab.com/nolith",
+ "status_tooltip_html": null,
+ "path": "/nolith"
+ },
+ "active": false,
+ "coverage": null,
+ "source": "pipeline",
+ "path": "/gitlab-com/gitlab-docs/pipelines/34993023",
+ "details": {
+ "status": {
+ "icon": "status_failed",
+ "text": "failed",
+ "label": "failed",
+ "group": "failed",
+ "tooltip": "failed",
+ "has_details": true,
+ "details_path": "/gitlab-com/gitlab-docs/pipelines/34993051",
+ "illustration": null,
+ "favicon": "https://gitlab.com/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png"
+ }
+ },
+ "project": {
+ "id": 1794617,
+ "name": "GitLab Docs",
+ "full_path": "/gitlab-com/gitlab-docs",
+ "full_name": "GitLab.com / GitLab Docs"
+ }
+ }
+ ]
+}
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
deleted file mode 100644
index 5ea3f85a247..00000000000
--- a/spec/javascripts/registry/components/app_spec.js
+++ /dev/null
@@ -1,129 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import Vue from 'vue';
-import registry from '~/registry/components/app.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { TEST_HOST } from 'spec/test_constants';
-import { reposServerResponse } from '../mock_data';
-
-describe('Registry List', () => {
- const Component = Vue.extend(registry);
- const props = {
- endpoint: `${TEST_HOST}/foo`,
- helpPagePath: 'foo',
- noContainersImage: 'foo',
- containersErrorImage: 'foo',
- repositoryUrl: 'foo',
- };
- let vm;
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- vm.$destroy();
- });
-
- describe('with data', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, reposServerResponse);
-
- vm = mountComponent(Component, { ...props });
- });
-
- it('should render a list of repos', done => {
- setTimeout(() => {
- expect(vm.$store.state.repos.length).toEqual(reposServerResponse.length);
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelectorAll('.container-image').length).toEqual(
- reposServerResponse.length,
- );
- done();
- });
- }, 0);
- });
-
- describe('delete repository', () => {
- it('should be possible to delete a repo', done => {
- setTimeout(() => {
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.container-image-head .js-remove-repo')).toBeDefined();
- done();
- });
- }, 0);
- });
- });
-
- describe('toggle repository', () => {
- it('should open the container', done => {
- setTimeout(() => {
- Vue.nextTick(() => {
- vm.$el.querySelector('.js-toggle-repo').click();
- Vue.nextTick(() => {
- expect(
- vm.$el.querySelector('.js-toggle-repo use').getAttribute('xlink:href'),
- ).toContain('angle-up');
- done();
- });
- });
- }, 0);
- });
- });
- });
-
- describe('without data', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
-
- vm = mountComponent(Component, { ...props });
- });
-
- it('should render empty message', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-no-container-images-text').textContent).toEqual(
- 'With the Container Registry, every project can have its own space to store its Docker images. More Information',
- );
- done();
- }, 0);
- });
- });
-
- describe('while loading data', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
-
- vm = mountComponent(Component, { ...props });
- });
-
- it('should render a loading spinner', done => {
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.gl-spinner')).not.toBe(null);
- done();
- });
- });
- });
-
- describe('invalid characters in path', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
-
- vm = mountComponent(Component, {
- ...props,
- characterError: true,
- });
- });
-
- it('should render invalid characters error message', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('p')).not.toContain(
- 'We are having trouble connecting to Docker, which could be due to an issue with your project name or path. More information',
- );
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/registry/components/collapsible_container_spec.js b/spec/javascripts/registry/components/collapsible_container_spec.js
deleted file mode 100644
index 2a5d8dd11da..00000000000
--- a/spec/javascripts/registry/components/collapsible_container_spec.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import Vue from 'vue';
-import collapsibleComponent from '~/registry/components/collapsible_container.vue';
-import store from '~/registry/stores';
-import * as types from '~/registry/stores/mutation_types';
-
-import { repoPropsData, registryServerResponse, reposServerResponse } from '../mock_data';
-
-describe('collapsible registry container', () => {
- let vm;
- let mock;
- const Component = Vue.extend(collapsibleComponent);
-
- const findDeleteBtn = () => vm.$el.querySelector('.js-remove-repo');
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
-
- mock.onGet(repoPropsData.tagsPath).replyOnce(200, registryServerResponse, {});
-
- store.commit(types.SET_REPOS_LIST, reposServerResponse);
-
- vm = new Component({
- store,
- propsData: {
- repo: repoPropsData,
- },
- }).$mount();
- });
-
- afterEach(() => {
- mock.restore();
- vm.$destroy();
- });
-
- describe('toggle', () => {
- it('should be closed by default', () => {
- expect(vm.$el.querySelector('.container-image-tags')).toBe(null);
- expect(vm.iconName).toEqual('angle-right');
- });
-
- it('should be open when user clicks on closed repo', done => {
- vm.$el.querySelector('.js-toggle-repo').click();
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.container-image-tags')).not.toBeNull();
- expect(vm.iconName).toEqual('angle-up');
-
- done();
- });
- });
-
- it('should be closed when the user clicks on an opened repo', done => {
- vm.$el.querySelector('.js-toggle-repo').click();
-
- Vue.nextTick(() => {
- vm.$el.querySelector('.js-toggle-repo').click();
- setTimeout(() => {
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.container-image-tags')).toBe(null);
- expect(vm.iconName).toEqual('angle-right');
- done();
- });
- });
- });
- });
- });
-
- describe('delete repo', () => {
- it('should be possible to delete a repo', () => {
- expect(findDeleteBtn()).not.toBeNull();
- });
-
- it('should call deleteItem when confirming deletion', done => {
- findDeleteBtn().click();
- spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
-
- Vue.nextTick(() => {
- document.querySelector(`#${vm.modalId} .btn-danger`).click();
-
- expect(vm.deleteItem).toHaveBeenCalledWith(vm.repo);
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js
deleted file mode 100644
index 9c7439206ef..00000000000
--- a/spec/javascripts/registry/components/table_registry_spec.js
+++ /dev/null
@@ -1,189 +0,0 @@
-import Vue from 'vue';
-import tableRegistry from '~/registry/components/table_registry.vue';
-import store from '~/registry/stores';
-import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { repoPropsData } from '../mock_data';
-
-const [firstImage, secondImage] = repoPropsData.list;
-
-describe('table registry', () => {
- let vm;
- const Component = Vue.extend(tableRegistry);
- const bulkDeletePath = 'path';
-
- const findDeleteBtn = () => vm.$el.querySelector('.js-delete-registry');
- const findDeleteBtnRow = () => vm.$el.querySelector('.js-delete-registry-row');
- const findSelectAllCheckbox = () => vm.$el.querySelector('.js-select-all-checkbox > input');
- const findAllRowCheckboxes = () =>
- Array.from(vm.$el.querySelectorAll('.js-select-checkbox input'));
- const confirmationModal = (child = '') => document.querySelector(`#${vm.modalId} ${child}`);
-
- const createComponent = () => {
- vm = mountComponentWithStore(Component, {
- store,
- props: {
- repo: repoPropsData,
- },
- });
- };
-
- const selectAllCheckboxes = () => vm.selectAll();
- const deselectAllCheckboxes = () => vm.deselectAll();
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('rendering', () => {
- it('should render a table with the registry list', () => {
- expect(vm.$el.querySelectorAll('table tbody tr').length).toEqual(repoPropsData.list.length);
- });
-
- it('should render registry tag', () => {
- const textRendered = vm.$el
- .querySelector('.table tbody tr')
- .textContent.trim()
- // replace additional whitespace characters (e.g. new lines) with a single empty space
- .replace(/\s\s+/g, ' ');
-
- expect(textRendered).toContain(repoPropsData.list[0].tag);
- expect(textRendered).toContain(repoPropsData.list[0].shortRevision);
- expect(textRendered).toContain(repoPropsData.list[0].layers);
- expect(textRendered).toContain(repoPropsData.list[0].size);
- });
- });
-
- describe('multi select', () => {
- it('should support multiselect and selecting a row should enable delete button', done => {
- findSelectAllCheckbox().click();
- selectAllCheckboxes();
-
- expect(findSelectAllCheckbox().checked).toBe(true);
-
- Vue.nextTick(() => {
- expect(findDeleteBtn().disabled).toBe(false);
- done();
- });
- });
-
- it('selecting all checkbox should select all rows and enable delete button', done => {
- selectAllCheckboxes();
-
- Vue.nextTick(() => {
- const checkedValues = findAllRowCheckboxes().filter(x => x.checked);
-
- expect(checkedValues.length).toBe(repoPropsData.list.length);
- done();
- });
- });
-
- it('deselecting select all checkbox should deselect all rows and disable delete button', done => {
- selectAllCheckboxes();
- deselectAllCheckboxes();
-
- Vue.nextTick(() => {
- const checkedValues = findAllRowCheckboxes().filter(x => x.checked);
-
- expect(checkedValues.length).toBe(0);
- done();
- });
- });
-
- it('should delete multiple items when multiple items are selected', done => {
- selectAllCheckboxes();
-
- Vue.nextTick(() => {
- expect(vm.itemsToBeDeleted).toEqual([0, 1]);
- expect(findDeleteBtn().disabled).toBe(false);
-
- findDeleteBtn().click();
- spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
-
- Vue.nextTick(() => {
- const modal = confirmationModal();
- confirmationModal('.btn-danger').click();
-
- expect(modal).toExist();
-
- Vue.nextTick(() => {
- expect(vm.itemsToBeDeleted).toEqual([]);
- expect(vm.multiDeleteItems).toHaveBeenCalledWith({
- path: bulkDeletePath,
- items: [firstImage.tag, secondImage.tag],
- });
- done();
- });
- });
- });
- });
- });
-
- describe('delete registry', () => {
- beforeEach(() => {
- vm.itemsToBeDeleted = [0];
- });
-
- it('should be possible to delete a registry', done => {
- Vue.nextTick(() => {
- expect(vm.itemsToBeDeleted).toEqual([0]);
- expect(findDeleteBtn()).toBeDefined();
- expect(findDeleteBtn().disabled).toBe(false);
- expect(findDeleteBtnRow()).toBeDefined();
- done();
- });
- });
-
- it('should call deleteItems and reset itemsToBeDeleted when confirming deletion', done => {
- Vue.nextTick(() => {
- expect(vm.itemsToBeDeleted).toEqual([0]);
- expect(findDeleteBtn().disabled).toBe(false);
- findDeleteBtn().click();
- spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
-
- Vue.nextTick(() => {
- confirmationModal('.btn-danger').click();
-
- expect(vm.itemsToBeDeleted).toEqual([]);
- expect(vm.multiDeleteItems).toHaveBeenCalledWith({
- path: bulkDeletePath,
- items: [firstImage.tag],
- });
- done();
- });
- });
- });
- });
-
- describe('pagination', () => {
- it('should be possible to change the page', () => {
- expect(vm.$el.querySelector('.gl-pagination')).toBeDefined();
- });
- });
-
- describe('modal content', () => {
- it('should show the singular title and image name when deleting a single image', done => {
- findDeleteBtnRow().click();
-
- Vue.nextTick(() => {
- expect(vm.modalTitle).toBe('Remove image');
- expect(vm.modalDescription).toContain(firstImage.tag);
- done();
- });
- });
-
- it('should show the plural title and image count when deleting more than one image', done => {
- selectAllCheckboxes();
- vm.setModalDescription();
-
- Vue.nextTick(() => {
- expect(vm.modalTitle).toBe('Remove images');
- expect(vm.modalDescription).toContain('<b>2</b> images');
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/registry/mock_data.js b/spec/javascripts/registry/mock_data.js
deleted file mode 100644
index 130ab298e89..00000000000
--- a/spec/javascripts/registry/mock_data.js
+++ /dev/null
@@ -1,134 +0,0 @@
-export const defaultState = {
- isLoading: false,
- endpoint: '',
- repos: [],
-};
-
-export const reposServerResponse = [
- {
- destroy_path: 'path',
- id: '123',
- location: 'location',
- path: 'foo',
- tags_path: 'tags_path',
- },
- {
- destroy_path: 'path_',
- id: '456',
- location: 'location_',
- path: 'bar',
- tags_path: 'tags_path_',
- },
-];
-
-export const registryServerResponse = [
- {
- name: 'centos7',
- short_revision: 'b118ab5b0',
- revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
- total_size: 679,
- layers: 19,
- location: 'location',
- created_at: 1505828744434,
- destroy_path: 'path_',
- },
- {
- name: 'centos6',
- short_revision: 'b118ab5b0',
- revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
- total_size: 679,
- layers: 19,
- location: 'location',
- created_at: 1505828744434,
- },
-];
-
-export const parsedReposServerResponse = [
- {
- canDelete: true,
- destroyPath: reposServerResponse[0].destroy_path,
- id: reposServerResponse[0].id,
- isLoading: false,
- list: [],
- location: reposServerResponse[0].location,
- name: reposServerResponse[0].path,
- tagsPath: reposServerResponse[0].tags_path,
- },
- {
- canDelete: true,
- destroyPath: reposServerResponse[1].destroy_path,
- id: reposServerResponse[1].id,
- isLoading: false,
- list: [],
- location: reposServerResponse[1].location,
- name: reposServerResponse[1].path,
- tagsPath: reposServerResponse[1].tags_path,
- },
-];
-
-export const parsedRegistryServerResponse = [
- {
- tag: registryServerResponse[0].name,
- revision: registryServerResponse[0].revision,
- shortRevision: registryServerResponse[0].short_revision,
- size: registryServerResponse[0].total_size,
- layers: registryServerResponse[0].layers,
- location: registryServerResponse[0].location,
- createdAt: registryServerResponse[0].created_at,
- destroyPath: registryServerResponse[0].destroy_path,
- canDelete: true,
- },
- {
- tag: registryServerResponse[1].name,
- revision: registryServerResponse[1].revision,
- shortRevision: registryServerResponse[1].short_revision,
- size: registryServerResponse[1].total_size,
- layers: registryServerResponse[1].layers,
- location: registryServerResponse[1].location,
- createdAt: registryServerResponse[1].created_at,
- destroyPath: registryServerResponse[1].destroy_path,
- canDelete: false,
- },
-];
-
-export const repoPropsData = {
- canDelete: true,
- destroyPath: 'path',
- id: '123',
- isLoading: false,
- list: [
- {
- tag: 'centos6',
- revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
- shortRevision: 'b118ab5b0',
- size: 19,
- layers: 10,
- location: 'location',
- createdAt: 1505828744434,
- destroyPath: 'path',
- canDelete: true,
- },
- {
- tag: 'test-image',
- revision: 'b969de599faea2b3d9b6605a8b0897261c571acaa36db1bdc7349b5775b4e0b4',
- shortRevision: 'b969de599',
- size: 19,
- layers: 10,
- location: 'location-2',
- createdAt: 1505828744434,
- destroyPath: 'path-2',
- canDelete: true,
- },
- ],
- location: 'location',
- name: 'foo',
- tagsPath: 'path',
- pagination: {
- perPage: 5,
- page: 1,
- total: 13,
- totalPages: 1,
- nextPage: null,
- previousPage: null,
- },
-};
diff --git a/spec/javascripts/registry/stores/actions_spec.js b/spec/javascripts/registry/stores/actions_spec.js
deleted file mode 100644
index 0613ec8e0f1..00000000000
--- a/spec/javascripts/registry/stores/actions_spec.js
+++ /dev/null
@@ -1,132 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import * as actions from '~/registry/stores/actions';
-import * as types from '~/registry/stores/mutation_types';
-import state from '~/registry/stores/state';
-import { TEST_HOST } from 'spec/test_constants';
-import testAction from '../../helpers/vuex_action_helper';
-import {
- reposServerResponse,
- registryServerResponse,
- parsedReposServerResponse,
-} from '../mock_data';
-
-describe('Actions Registry Store', () => {
- let mockedState;
- let mock;
-
- beforeEach(() => {
- mockedState = state();
- mockedState.endpoint = `${TEST_HOST}/endpoint.json`;
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('server requests', () => {
- describe('fetchRepos', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, reposServerResponse, {});
- });
-
- it('should set receveived repos', done => {
- testAction(
- actions.fetchRepos,
- null,
- mockedState,
- [
- { type: types.TOGGLE_MAIN_LOADING },
- { type: types.TOGGLE_MAIN_LOADING },
- { type: types.SET_REPOS_LIST, payload: reposServerResponse },
- ],
- [],
- done,
- );
- });
- });
-
- describe('fetchList', () => {
- let repo;
- beforeEach(() => {
- mockedState.repos = parsedReposServerResponse;
- [, repo] = mockedState.repos;
-
- mock.onGet(repo.tagsPath).replyOnce(200, registryServerResponse, {});
- });
-
- it('should set received list', done => {
- testAction(
- actions.fetchList,
- { repo },
- mockedState,
- [
- { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo },
- { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo },
- {
- type: types.SET_REGISTRY_LIST,
- payload: {
- repo,
- resp: registryServerResponse,
- headers: jasmine.anything(),
- },
- },
- ],
- [],
- done,
- );
- });
- });
- });
-
- describe('setMainEndpoint', () => {
- it('should commit set main endpoint', done => {
- testAction(
- actions.setMainEndpoint,
- 'endpoint',
- mockedState,
- [{ type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' }],
- [],
- done,
- );
- });
- });
-
- describe('toggleLoading', () => {
- it('should commit toggle main loading', done => {
- testAction(
- actions.toggleLoading,
- null,
- mockedState,
- [{ type: types.TOGGLE_MAIN_LOADING }],
- [],
- done,
- );
- });
- });
-
- describe('deleteItem', () => {
- it('should perform DELETE request on destroyPath', done => {
- const destroyPath = `${TEST_HOST}/mygroup/myproject/container_registry/1.json`;
- let deleted = false;
- mock.onDelete(destroyPath).replyOnce(() => {
- deleted = true;
- return [200];
- });
- testAction(
- actions.deleteItem,
- {
- destroyPath,
- },
- mockedState,
- )
- .then(() => {
- expect(mock.history.delete.length).toBe(1);
- expect(deleted).toBe(true);
- done();
- })
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/registry/stores/mutations_spec.js b/spec/javascripts/registry/stores/mutations_spec.js
deleted file mode 100644
index e19fe7a27cf..00000000000
--- a/spec/javascripts/registry/stores/mutations_spec.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import mutations from '~/registry/stores/mutations';
-import * as types from '~/registry/stores/mutation_types';
-import {
- defaultState,
- reposServerResponse,
- registryServerResponse,
- parsedReposServerResponse,
- parsedRegistryServerResponse,
-} from '../mock_data';
-
-describe('Mutations Registry Store', () => {
- let mockState;
- beforeEach(() => {
- mockState = defaultState;
- });
-
- describe('SET_MAIN_ENDPOINT', () => {
- it('should set the main endpoint', () => {
- const expectedState = Object.assign({}, mockState, { endpoint: 'foo' });
- mutations[types.SET_MAIN_ENDPOINT](mockState, 'foo');
-
- expect(mockState).toEqual(expectedState);
- });
- });
-
- describe('SET_REPOS_LIST', () => {
- it('should set a parsed repository list', () => {
- mutations[types.SET_REPOS_LIST](mockState, reposServerResponse);
-
- expect(mockState.repos).toEqual(parsedReposServerResponse);
- });
- });
-
- describe('TOGGLE_MAIN_LOADING', () => {
- it('should set a parsed repository list', () => {
- mutations[types.TOGGLE_MAIN_LOADING](mockState);
-
- expect(mockState.isLoading).toEqual(true);
- });
- });
-
- describe('SET_REGISTRY_LIST', () => {
- it('should set a list of registries in a specific repository', () => {
- mutations[types.SET_REPOS_LIST](mockState, reposServerResponse);
- mutations[types.SET_REGISTRY_LIST](mockState, {
- repo: mockState.repos[0],
- resp: registryServerResponse,
- headers: {
- 'x-per-page': 2,
- 'x-page': 1,
- 'x-total': 10,
- },
- });
-
- expect(mockState.repos[0].list).toEqual(parsedRegistryServerResponse);
- expect(mockState.repos[0].pagination).toEqual({
- perPage: 2,
- page: 1,
- total: 10,
- totalPages: NaN,
- nextPage: NaN,
- previousPage: NaN,
- });
- });
- });
-
- describe('TOGGLE_REGISTRY_LIST_LOADING', () => {
- it('should toggle isLoading property for a specific repository', () => {
- mutations[types.SET_REPOS_LIST](mockState, reposServerResponse);
- mutations[types.SET_REGISTRY_LIST](mockState, {
- repo: mockState.repos[0],
- resp: registryServerResponse,
- headers: {
- 'x-per-page': 2,
- 'x-page': 1,
- 'x-total': 10,
- },
- });
-
- mutations[types.TOGGLE_REGISTRY_LIST_LOADING](mockState, mockState.repos[0]);
-
- expect(mockState.repos[0].isLoading).toEqual(true);
- });
- });
-});
diff --git a/spec/javascripts/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js
deleted file mode 100644
index 11a385fa64d..00000000000
--- a/spec/javascripts/releases/components/release_block_spec.js
+++ /dev/null
@@ -1,170 +0,0 @@
-import Vue from 'vue';
-import component from '~/releases/components/release_block.vue';
-import timeagoMixin from '~/vue_shared/mixins/timeago';
-
-import mountComponent from '../../helpers/vue_mount_component_helper';
-
-describe('Release block', () => {
- const Component = Vue.extend(component);
-
- const release = {
- name: 'Bionic Beaver',
- tag_name: '18.04',
- description: '## changelog\n\n* line 1\n* line2',
- description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>',
- author_name: 'Release bot',
- author_email: 'release-bot@example.com',
- released_at: '2012-05-28T05:00:00-07:00',
- author: {
- avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png',
- id: 482476,
- name: 'John Doe',
- path: '/johndoe',
- state: 'active',
- status_tooltip_html: null,
- username: 'johndoe',
- web_url: 'https://gitlab.com/johndoe',
- },
- commit: {
- id: '2695effb5807a22ff3d138d593fd856244e155e7',
- short_id: '2695effb',
- title: 'Initial commit',
- created_at: '2017-07-26T11:08:53.000+02:00',
- parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'],
- message: 'Initial commit',
- author_name: 'John Smith',
- author_email: 'john@example.com',
- authored_date: '2012-05-28T04:42:42-07:00',
- committer_name: 'Jack Smith',
- committer_email: 'jack@example.com',
- committed_date: '2012-05-28T04:42:42-07:00',
- },
- assets: {
- count: 6,
- sources: [
- {
- format: 'zip',
- url:
- 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.zip',
- },
- {
- format: 'tar.gz',
- url:
- 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz',
- },
- {
- format: 'tar.bz2',
- url:
- 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2',
- },
- {
- format: 'tar',
- url:
- 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar',
- },
- ],
- links: [
- {
- name: 'release-18.04.dmg',
- url: 'https://my-external-hosting.example.com/scrambled-url/',
- external: true,
- },
- {
- name: 'binary-linux-amd64',
- url:
- 'https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50',
- external: false,
- },
- ],
- },
- };
- let vm;
-
- const factory = props => mountComponent(Component, { release: props });
-
- beforeEach(() => {
- vm = factory(release);
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it("renders the block with an id equal to the release's tag name", () => {
- expect(vm.$el.id).toBe('18.04');
- });
-
- it('renders release name', () => {
- expect(vm.$el.textContent).toContain(release.name);
- });
-
- it('renders commit sha', () => {
- expect(vm.$el.textContent).toContain(release.commit.short_id);
- });
-
- it('renders tag name', () => {
- expect(vm.$el.textContent).toContain(release.tag_name);
- });
-
- it('renders release date', () => {
- expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.released_at));
- });
-
- it('renders number of assets provided', () => {
- expect(vm.$el.querySelector('.js-assets-count').textContent).toContain(release.assets.count);
- });
-
- it('renders dropdown with the sources', () => {
- expect(vm.$el.querySelectorAll('.js-sources-dropdown li').length).toEqual(
- release.assets.sources.length,
- );
-
- expect(vm.$el.querySelector('.js-sources-dropdown li a').getAttribute('href')).toEqual(
- release.assets.sources[0].url,
- );
-
- expect(vm.$el.querySelector('.js-sources-dropdown li a').textContent).toContain(
- release.assets.sources[0].format,
- );
- });
-
- it('renders list with the links provided', () => {
- expect(vm.$el.querySelectorAll('.js-assets-list li').length).toEqual(
- release.assets.links.length,
- );
-
- expect(vm.$el.querySelector('.js-assets-list li a').getAttribute('href')).toEqual(
- release.assets.links[0].url,
- );
-
- expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain(
- release.assets.links[0].name,
- );
- });
-
- it('renders author avatar', () => {
- expect(vm.$el.querySelector('.user-avatar-link')).not.toBeNull();
- });
-
- describe('external label', () => {
- it('renders external label when link is external', () => {
- expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain('external source');
- });
-
- it('does not render external label when link is not external', () => {
- expect(vm.$el.querySelector('.js-assets-list li:nth-child(2) a').textContent).not.toContain(
- 'external source',
- );
- });
- });
-
- describe('with upcoming_release flag', () => {
- beforeEach(() => {
- vm = factory(Object.assign({}, release, { upcoming_release: true }));
- });
-
- it('renders upcoming release badge', () => {
- expect(vm.$el.textContent).toContain('Upcoming Release');
- });
- });
-});
diff --git a/spec/javascripts/releases/components/app_spec.js b/spec/javascripts/releases/list/components/app_spec.js
index f30c7685e34..471c442e497 100644
--- a/spec/javascripts/releases/components/app_spec.js
+++ b/spec/javascripts/releases/list/components/app_spec.js
@@ -1,10 +1,10 @@
import Vue from 'vue';
-import app from '~/releases/components/app.vue';
-import createStore from '~/releases/store';
+import app from '~/releases/list/components/app.vue';
+import createStore from '~/releases/list/store';
import api from '~/api';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../store/helpers';
-import { releases } from '../mock_data';
+import { releases } from '../../mock_data';
describe('Releases App ', () => {
const Component = Vue.extend(app);
diff --git a/spec/javascripts/releases/store/actions_spec.js b/spec/javascripts/releases/list/store/actions_spec.js
index 6eb8e681be9..8e78a631a5f 100644
--- a/spec/javascripts/releases/store/actions_spec.js
+++ b/spec/javascripts/releases/list/store/actions_spec.js
@@ -3,12 +3,12 @@ import {
fetchReleases,
receiveReleasesSuccess,
receiveReleasesError,
-} from '~/releases/store/actions';
-import state from '~/releases/store/state';
-import * as types from '~/releases/store/mutation_types';
+} from '~/releases/list/store/actions';
+import state from '~/releases/list/store/state';
+import * as types from '~/releases/list/store/mutation_types';
import api from '~/api';
import testAction from 'spec/helpers/vuex_action_helper';
-import { releases } from '../mock_data';
+import { releases } from '../../mock_data';
describe('Releases State actions', () => {
let mockedState;
diff --git a/spec/javascripts/releases/store/helpers.js b/spec/javascripts/releases/list/store/helpers.js
index e962b254377..fbc89ec2148 100644
--- a/spec/javascripts/releases/store/helpers.js
+++ b/spec/javascripts/releases/list/store/helpers.js
@@ -1,4 +1,4 @@
-import state from '~/releases/store/state';
+import state from '~/releases/list/store/state';
// eslint-disable-next-line import/prefer-default-export
export const resetStore = store => {
diff --git a/spec/javascripts/releases/store/mutations_spec.js b/spec/javascripts/releases/list/store/mutations_spec.js
index 72b98529fe9..d2577891495 100644
--- a/spec/javascripts/releases/store/mutations_spec.js
+++ b/spec/javascripts/releases/list/store/mutations_spec.js
@@ -1,7 +1,7 @@
-import state from '~/releases/store/state';
-import mutations from '~/releases/store/mutations';
-import * as types from '~/releases/store/mutation_types';
-import { releases } from '../mock_data';
+import state from '~/releases/list/store/state';
+import mutations from '~/releases/list/store/mutations';
+import * as types from '~/releases/list/store/mutation_types';
+import { releases } from '../../mock_data';
describe('Releases Store Mutations', () => {
let stateCopy;
diff --git a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
index 1f1e626ed33..1b006cdbd4e 100644
--- a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
@@ -83,11 +83,11 @@ describe('Grouped Test Reports App', () => {
setTimeout(() => {
expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
- 'Test summary contained 2 failed test results out of 11 total tests',
+ 'Test summary contained 2 failed/error test results out of 11 total tests',
);
expect(vm.$el.textContent).toContain(
- 'rspec:pg found 2 failed test results out of 8 total tests',
+ 'rspec:pg found 2 failed/error test results out of 8 total tests',
);
expect(vm.$el.textContent).toContain('New');
@@ -111,16 +111,16 @@ describe('Grouped Test Reports App', () => {
setTimeout(() => {
expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
- 'Test summary contained 2 failed test results and 2 fixed test results out of 11 total tests',
+ 'Test summary contained 2 failed/error test results and 2 fixed test results out of 11 total tests',
);
expect(vm.$el.textContent).toContain(
- 'rspec:pg found 1 failed test result and 2 fixed test results out of 8 total tests',
+ 'rspec:pg found 1 failed/error test result and 2 fixed test results out of 8 total tests',
);
expect(vm.$el.textContent).toContain('New');
expect(vm.$el.textContent).toContain(
- ' java ant found 1 failed test result out of 3 total tests',
+ ' java ant found 1 failed/error test result out of 3 total tests',
);
done();
}, 0);
diff --git a/spec/javascripts/sidebar/assignee_title_spec.js b/spec/javascripts/sidebar/assignee_title_spec.js
index 7fff7c075d9..6c65a55ff29 100644
--- a/spec/javascripts/sidebar/assignee_title_spec.js
+++ b/spec/javascripts/sidebar/assignee_title_spec.js
@@ -1,13 +1,12 @@
import Vue from 'vue';
import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue';
+import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
describe('AssigneeTitle component', () => {
let component;
let AssigneeTitleComponent;
- let statsSpy;
beforeEach(() => {
- statsSpy = spyOnDependency(AssigneeTitle, 'trackEvent');
AssigneeTitleComponent = Vue.extend(AssigneeTitle);
});
@@ -105,15 +104,20 @@ describe('AssigneeTitle component', () => {
expect(component.$el.querySelector('.edit-link')).not.toBeNull();
});
- it('calls trackEvent when edit is clicked', () => {
+ it('tracks the event when edit is clicked', () => {
component = new AssigneeTitleComponent({
propsData: {
numberOfAssignees: 0,
editable: true,
},
}).$mount();
- component.$el.querySelector('.js-sidebar-dropdown-toggle').click();
- expect(statsSpy).toHaveBeenCalled();
+ const spy = mockTracking('_category_', component.$el, spyOn);
+ triggerEvent('.js-sidebar-dropdown-toggle');
+
+ expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', {
+ label: 'right_sidebar',
+ property: 'assignee',
+ });
});
});
diff --git a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js
index 2e1863cff86..ab28190ae64 100644
--- a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js
+++ b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js
@@ -83,8 +83,8 @@ describe('Issuable Time Tracker', () => {
initTimeTrackingComponent({
timeEstimate: 100000, // 1d 3h
timeSpent: 5000, // 1h 23m
- timeEstimateHumanReadable: '',
- timeSpentHumanReadable: '',
+ timeEstimateHumanReadable: '1d 3h',
+ timeSpentHumanReadable: '1h 23m',
});
});
@@ -98,6 +98,16 @@ describe('Issuable Time Tracker', () => {
});
});
+ it('should show full times when the sidebar is collapsed', done => {
+ Vue.nextTick(() => {
+ const timeTrackingText = vm.$el.querySelector('.time-tracking-collapsed-summary span')
+ .innerText;
+
+ expect(timeTrackingText).toBe('1h 23m / 1d 3h');
+ done();
+ });
+ });
+
describe('Remaining meter', () => {
it('should display the remaining meter with the correct width', done => {
Vue.nextTick(() => {
diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
deleted file mode 100644
index ea9e5677bc5..00000000000
--- a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import Vue from 'vue';
-import confidentialIssueSidebar from '~/sidebar/components/confidential/confidential_issue_sidebar.vue';
-
-describe('Confidential Issue Sidebar Block', () => {
- let vm1;
- let vm2;
- let statsSpy;
-
- beforeEach(() => {
- statsSpy = spyOnDependency(confidentialIssueSidebar, 'trackEvent');
- const Component = Vue.extend(confidentialIssueSidebar);
- const service = {
- update: () => Promise.resolve(true),
- };
-
- vm1 = new Component({
- propsData: {
- isConfidential: true,
- isEditable: true,
- service,
- },
- }).$mount();
-
- vm2 = new Component({
- propsData: {
- isConfidential: false,
- isEditable: false,
- service,
- },
- }).$mount();
- });
-
- it('shows if confidential and/or editable', () => {
- expect(vm1.$el.innerHTML.includes('Edit')).toBe(true);
-
- expect(vm1.$el.innerHTML.includes('This issue is confidential')).toBe(true);
-
- expect(vm2.$el.innerHTML.includes('Not confidential')).toBe(true);
- });
-
- it('displays the edit form when editable', done => {
- expect(vm1.edit).toBe(false);
-
- vm1.$el.querySelector('.confidential-edit').click();
-
- expect(vm1.edit).toBe(true);
-
- setTimeout(() => {
- expect(vm1.$el.innerHTML.includes('You are going to turn off the confidentiality.')).toBe(
- true,
- );
-
- done();
- });
- });
-
- it('displays the edit form when opened from collapsed state', done => {
- expect(vm1.edit).toBe(false);
-
- vm1.$el.querySelector('.sidebar-collapsed-icon').click();
-
- expect(vm1.edit).toBe(true);
-
- setTimeout(() => {
- expect(vm1.$el.innerHTML.includes('You are going to turn off the confidentiality.')).toBe(
- true,
- );
-
- done();
- });
- });
-
- it('calls trackEvent when "Edit" is clicked', () => {
- vm1.$el.querySelector('.confidential-edit').click();
-
- expect(statsSpy).toHaveBeenCalled();
- });
-});
diff --git a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
index 2d930428230..decccbb8964 100644
--- a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
@@ -1,13 +1,12 @@
import Vue from 'vue';
import lockIssueSidebar from '~/sidebar/components/lock/lock_issue_sidebar.vue';
+import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
describe('LockIssueSidebar', () => {
let vm1;
let vm2;
- let statsSpy;
beforeEach(() => {
- statsSpy = spyOnDependency(lockIssueSidebar, 'trackEvent');
const Component = Vue.extend(lockIssueSidebar);
const mediator = {
@@ -61,10 +60,14 @@ describe('LockIssueSidebar', () => {
});
});
- it('calls trackEvent when "Edit" is clicked', () => {
- vm1.$el.querySelector('.lock-edit').click();
+ it('tracks an event when "Edit" is clicked', () => {
+ const spy = mockTracking('_category_', vm1.$el, spyOn);
+ triggerEvent('.lock-edit');
- expect(statsSpy).toHaveBeenCalled();
+ expect(spy).toHaveBeenCalledWith('_category_', 'click_edit_button', {
+ label: 'right_sidebar',
+ property: 'lock_issue',
+ });
});
it('displays the edit form when opened from collapsed state', done => {
diff --git a/spec/javascripts/sidebar/subscriptions_spec.js b/spec/javascripts/sidebar/subscriptions_spec.js
index 2efa13f3fe8..a97608d6b8a 100644
--- a/spec/javascripts/sidebar/subscriptions_spec.js
+++ b/spec/javascripts/sidebar/subscriptions_spec.js
@@ -2,14 +2,13 @@ import Vue from 'vue';
import subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue';
import eventHub from '~/sidebar/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { mockTracking } from 'spec/helpers/tracking_helper';
describe('Subscriptions', function() {
let vm;
let Subscriptions;
- let statsSpy;
beforeEach(() => {
- statsSpy = spyOnDependency(subscriptions, 'trackEvent');
Subscriptions = Vue.extend(subscriptions);
});
@@ -53,6 +52,7 @@ describe('Subscriptions', function() {
vm = mountComponent(Subscriptions, { subscribed: true });
spyOn(eventHub, '$emit');
spyOn(vm, '$emit');
+ spyOn(vm, 'track');
vm.toggleSubscription();
@@ -60,11 +60,12 @@ describe('Subscriptions', function() {
expect(vm.$emit).toHaveBeenCalledWith('toggleSubscription', jasmine.any(Object));
});
- it('calls trackEvent when toggled', () => {
+ it('tracks the event when toggled', () => {
vm = mountComponent(Subscriptions, { subscribed: true });
+ const spy = mockTracking('_category_', vm.$el, spyOn);
vm.toggleSubscription();
- expect(statsSpy).toHaveBeenCalled();
+ expect(spy).toHaveBeenCalled();
});
it('onClickCollapsedIcon method emits `toggleSidebar` event on component', () => {
diff --git a/spec/javascripts/sidebar/todo_spec.js b/spec/javascripts/sidebar/todo_spec.js
deleted file mode 100644
index e7abd19c865..00000000000
--- a/spec/javascripts/sidebar/todo_spec.js
+++ /dev/null
@@ -1,171 +0,0 @@
-import Vue from 'vue';
-
-import SidebarTodos from '~/sidebar/components/todo_toggle/todo.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-const createComponent = ({
- issuableId = 1,
- issuableType = 'epic',
- isTodo,
- isActionActive,
- collapsed,
-}) => {
- const Component = Vue.extend(SidebarTodos);
-
- return mountComponent(Component, {
- issuableId,
- issuableType,
- isTodo,
- isActionActive,
- collapsed,
- });
-};
-
-describe('SidebarTodo', () => {
- let vm;
-
- beforeEach(() => {
- vm = createComponent({});
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('computed', () => {
- describe('buttonClasses', () => {
- it('returns todo button classes for when `collapsed` prop is `false`', () => {
- expect(vm.buttonClasses).toBe('btn btn-default btn-todo issuable-header-btn float-right');
- });
-
- it('returns todo button classes for when `collapsed` prop is `true`', done => {
- vm.collapsed = true;
- Vue.nextTick()
- .then(() => {
- expect(vm.buttonClasses).toBe(
- 'btn-blank btn-todo sidebar-collapsed-icon dont-change-state',
- );
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('buttonLabel', () => {
- it('returns todo button text for marking todo as done when `isTodo` prop is `true`', () => {
- expect(vm.buttonLabel).toBe('Mark as done');
- });
-
- it('returns todo button text for add todo when `isTodo` prop is `false`', done => {
- vm.isTodo = false;
- Vue.nextTick()
- .then(() => {
- expect(vm.buttonLabel).toBe('Add a To Do');
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('collapsedButtonIconClasses', () => {
- it('returns collapsed button icon class when `isTodo` prop is `true`', () => {
- expect(vm.collapsedButtonIconClasses).toBe('todo-undone');
- });
-
- it('returns empty string when `isTodo` prop is `false`', done => {
- vm.isTodo = false;
- Vue.nextTick()
- .then(() => {
- expect(vm.collapsedButtonIconClasses).toBe('');
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
- describe('collapsedButtonIcon', () => {
- it('returns button icon name when `isTodo` prop is `true`', () => {
- expect(vm.collapsedButtonIcon).toBe('todo-done');
- });
-
- it('returns button icon name when `isTodo` prop is `false`', done => {
- vm.isTodo = false;
- Vue.nextTick()
- .then(() => {
- expect(vm.collapsedButtonIcon).toBe('todo-add');
- })
- .then(done)
- .catch(done.fail);
- });
- });
- });
-
- describe('methods', () => {
- describe('handleButtonClick', () => {
- it('emits `toggleTodo` event on component', () => {
- spyOn(vm, '$emit');
- vm.handleButtonClick();
-
- expect(vm.$emit).toHaveBeenCalledWith('toggleTodo');
- });
- });
- });
-
- describe('template', () => {
- it('renders component container element', () => {
- const dataAttributes = {
- issuableId: '1',
- issuableType: 'epic',
- originalTitle: '',
- placement: 'left',
- container: 'body',
- boundary: 'viewport',
- };
-
- expect(vm.$el.nodeName).toBe('BUTTON');
-
- const elDataAttrs = vm.$el.dataset;
- Object.keys(elDataAttrs).forEach(attr => {
- expect(elDataAttrs[attr]).toBe(dataAttributes[attr]);
- });
- });
-
- it('check button label computed property', () => {
- expect(vm.buttonLabel).toEqual('Mark as done');
- });
-
- it('renders button label element when `collapsed` prop is `false`', () => {
- const buttonLabelEl = vm.$el.querySelector('span.issuable-todo-inner');
-
- expect(buttonLabelEl).not.toBeNull();
- expect(buttonLabelEl.innerText.trim()).toBe('Mark as done');
- });
-
- it('renders button icon when `collapsed` prop is `true`', done => {
- vm.collapsed = true;
- Vue.nextTick()
- .then(() => {
- const buttonIconEl = vm.$el.querySelector('svg');
-
- expect(buttonIconEl).not.toBeNull();
- expect(buttonIconEl.querySelector('use').getAttribute('xlink:href')).toContain(
- 'todo-done',
- );
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('renders loading icon when `isActionActive` prop is true', done => {
- vm.isActionActive = true;
- Vue.nextTick()
- .then(() => {
- const loadingEl = vm.$el.querySelector('span.loading-container');
-
- expect(loadingEl).not.toBeNull();
- })
- .then(done)
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index c0a999cfaa6..cb6b158f01c 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -7,7 +7,6 @@ import 'core-js/features/set-immediate';
import 'vendor/jasmine-jquery';
import '~/commons';
import Vue from 'vue';
-import VueResource from 'vue-resource';
import Translate from '~/vue_shared/translate';
import jasmineDiff from 'jasmine-diff';
import { config as testUtilsConfig } from '@vue/test-utils';
@@ -46,7 +45,6 @@ Vue.config.errorHandler = function(err) {
fail(err);
};
-Vue.use(VueResource);
Vue.use(Translate);
// enable test fixtures
@@ -72,7 +70,7 @@ window.gl = window.gl || {};
window.gl.TEST_HOST = TEST_HOST;
window.gon = window.gon || {};
window.gon.test_env = true;
-window.gon.ee = process.env.IS_GITLAB_EE;
+window.gon.ee = process.env.IS_EE;
gon.relative_url_root = '';
let hasUnhandledPromiseRejections = false;
@@ -102,13 +100,6 @@ afterEach(__rewire_reset_all__); // eslint-disable-line
// to run our unit tests.
beforeEach(done => done());
-const builtinVueHttpInterceptors = Vue.http.interceptors.slice();
-
-beforeEach(() => {
- // restore interceptors so we have no remaining ones from previous tests
- Vue.http.interceptors = builtinVueHttpInterceptors.slice();
-});
-
let longRunningTestTimeoutHandle;
beforeEach(done => {
@@ -127,7 +118,7 @@ const axiosDefaultAdapter = getDefaultAdapter();
// render all of our tests
const testContexts = [require.context('spec', true, /_spec$/)];
-if (process.env.IS_GITLAB_EE) {
+if (process.env.IS_EE) {
testContexts.push(require.context('ee_spec', true, /_spec$/));
}
@@ -216,7 +207,7 @@ if (process.env.BABEL_ENV === 'coverage') {
describe('Uncovered files', function() {
const sourceFilesContexts = [require.context('~', true, /\.(js|vue)$/)];
- if (process.env.IS_GITLAB_EE) {
+ if (process.env.IS_EE) {
sourceFilesContexts.push(require.context('ee', true, /\.(js|vue)$/));
}
diff --git a/spec/javascripts/test_constants.js b/spec/javascripts/test_constants.js
index c97d47a6406..51c0716b99d 100644
--- a/spec/javascripts/test_constants.js
+++ b/spec/javascripts/test_constants.js
@@ -1,7 +1 @@
-export const FIXTURES_PATH = `/fixtures`;
-export const TEST_HOST = 'http://test.host';
-
-export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`;
-
-export const GREEN_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/green_box.png`;
-export const RED_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/red_box.png`;
+export * from '../frontend/helpers/test_constants';
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
index 802f54f6a7e..dc3c547c632 100644
--- a/spec/javascripts/todos_spec.js
+++ b/spec/javascripts/todos_spec.js
@@ -1,18 +1,31 @@
import $ from 'jquery';
+import MockAdapter from 'axios-mock-adapter';
import Todos from '~/pages/dashboard/todos/index/todos';
import '~/lib/utils/common_utils';
+import '~/gl_dropdown';
+import axios from '~/lib/utils/axios_utils';
+import { addDelimiter } from '~/lib/utils/text_utility';
+
+const TEST_COUNT_BIG = 2000;
+const TEST_DONE_COUNT_BIG = 7300;
describe('Todos', () => {
preloadFixtures('todos/todos.html');
let todoItem;
+ let mock;
beforeEach(() => {
loadFixtures('todos/todos.html');
todoItem = document.querySelector('.todos-list .todo');
+ mock = new MockAdapter(axios);
return new Todos();
});
+ afterEach(() => {
+ mock.restore();
+ });
+
describe('goToTodoUrl', () => {
it('opens the todo url', done => {
const todoLink = todoItem.dataset.url;
@@ -53,5 +66,43 @@ describe('Todos', () => {
expect(windowOpenSpy).not.toHaveBeenCalled();
});
});
+
+ describe('on done todo click', () => {
+ let onToggleSpy;
+
+ beforeEach(done => {
+ const el = document.querySelector('.js-done-todo');
+ const path = el.dataset.href;
+
+ // Arrange
+ mock
+ .onDelete(path)
+ .replyOnce(200, { count: TEST_COUNT_BIG, done_count: TEST_DONE_COUNT_BIG });
+ onToggleSpy = jasmine.createSpy('onToggle');
+ $(document).on('todo:toggle', onToggleSpy);
+
+ // Act
+ el.click();
+
+ // Wait for axios and HTML to udpate
+ setImmediate(done);
+ });
+
+ it('dispatches todo:toggle', () => {
+ expect(onToggleSpy).toHaveBeenCalledWith(jasmine.anything(), TEST_COUNT_BIG);
+ });
+
+ it('updates pending text', () => {
+ expect(document.querySelector('.todos-pending .badge').innerHTML).toEqual(
+ addDelimiter(TEST_COUNT_BIG),
+ );
+ });
+
+ it('updates done text', () => {
+ expect(document.querySelector('.todos-done .badge').innerHTML).toEqual(
+ addDelimiter(TEST_DONE_COUNT_BIG),
+ );
+ });
+ });
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
index 42bf3b7df09..1949bee1406 100644
--- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
@@ -207,7 +207,7 @@ describe('Deployment component', () => {
it('renders the link to the review app without dropdown', () => {
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
- expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-deploy-url')).not.toBeNull();
});
});
@@ -223,12 +223,12 @@ describe('Deployment component', () => {
it('renders the link to the review app without dropdown', () => {
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
- expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-deploy-url')).not.toBeNull();
});
it('renders the link to the review app linked to to the first change', () => {
const expectedUrl = deploymentMockData.changes[0].external_url;
- const deployUrl = vm.$el.querySelector('.js-deploy-url-feature-flag');
+ const deployUrl = vm.$el.querySelector('.js-deploy-url');
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
expect(deployUrl).not.toBeNull();
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js
index dfbc68c48b9..6cdf60f3535 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_container_spec.js
@@ -1,6 +1,7 @@
import { mount, createLocalVue } from '@vue/test-utils';
import MrWidgetPipelineContainer from '~/vue_merge_request_widget/components/mr_widget_pipeline_container.vue';
import MrWidgetPipeline from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import ArtifactsApp from '~/vue_merge_request_widget/components/artifacts_list_app.vue';
import { mockStore } from '../mock_data';
describe('MrWidgetPipelineContainer', () => {
@@ -87,4 +88,10 @@ describe('MrWidgetPipelineContainer', () => {
expect(deployments.wrappers.map(x => x.props())).toEqual(expectedProps);
});
});
+
+ describe('with artifacts path', () => {
+ it('renders the artifacts app', () => {
+ expect(wrapper.find(ArtifactsApp).isVisible()).toBe(true);
+ });
+ });
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js
index 7b1d589dcf8..5844dad42ff 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_wip_spec.js
@@ -47,7 +47,7 @@ describe('Wip', () => {
it('should make a request to service and handle response', done => {
const vm = createComponent();
- spyOn(window, 'Flash').and.returnValue(true);
+ const flashSpy = spyOnDependency(WorkInProgress, 'createFlash').and.returnValue(true);
spyOn(eventHub, '$emit');
spyOn(vm.service, 'removeWIP').and.returnValue(
new Promise(resolve => {
@@ -61,10 +61,7 @@ describe('Wip', () => {
setTimeout(() => {
expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
- expect(window.Flash).toHaveBeenCalledWith(
- 'The merge request can now be merged.',
- 'notice',
- );
+ expect(flashSpy).toHaveBeenCalledWith('The merge request can now be merged.', 'notice');
done();
}, 333);
});
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 2f79806652b..089ec08fbf9 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -289,4 +289,5 @@ export const mockStore = {
troubleshootingDocsPath: 'troubleshooting-docs-path',
ciStatus: 'ci-status',
hasCI: true,
+ exposedArtifactsPath: 'exposed_artifacts.json',
};
diff --git a/spec/javascripts/vue_mr_widget/stores/artifacts_list/actions_spec.js b/spec/javascripts/vue_mr_widget/stores/artifacts_list/actions_spec.js
new file mode 100644
index 00000000000..4c4bebcb4cd
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/stores/artifacts_list/actions_spec.js
@@ -0,0 +1,165 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import {
+ setEndpoint,
+ requestArtifacts,
+ clearEtagPoll,
+ stopPolling,
+ fetchArtifacts,
+ receiveArtifactsSuccess,
+ receiveArtifactsError,
+} from '~/vue_merge_request_widget/stores/artifacts_list/actions';
+import state from '~/vue_merge_request_widget/stores/artifacts_list/state';
+import * as types from '~/vue_merge_request_widget/stores/artifacts_list/mutation_types';
+import testAction from 'spec/helpers/vuex_action_helper';
+import { TEST_HOST } from 'spec/test_constants';
+
+describe('Artifacts App Store Actions', () => {
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = state();
+ });
+
+ describe('setEndpoint', () => {
+ it('should commit SET_ENDPOINT mutation', done => {
+ testAction(
+ setEndpoint,
+ 'endpoint.json',
+ mockedState,
+ [{ type: types.SET_ENDPOINT, payload: 'endpoint.json' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestArtifacts', () => {
+ it('should commit REQUEST_ARTIFACTS mutation', done => {
+ testAction(
+ requestArtifacts,
+ null,
+ mockedState,
+ [{ type: types.REQUEST_ARTIFACTS }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchArtifacts', () => {
+ let mock;
+
+ beforeEach(() => {
+ mockedState.endpoint = `${TEST_HOST}/endpoint.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ stopPolling();
+ clearEtagPoll();
+ });
+
+ describe('success', () => {
+ it('dispatches requestArtifacts and receiveArtifactsSuccess ', done => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [
+ {
+ text: 'result.txt',
+ url: 'asda',
+ job_name: 'generate-artifact',
+ job_path: 'asda',
+ },
+ ]);
+
+ testAction(
+ fetchArtifacts,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestArtifacts',
+ },
+ {
+ payload: {
+ data: [
+ {
+ text: 'result.txt',
+ url: 'asda',
+ job_name: 'generate-artifact',
+ job_path: 'asda',
+ },
+ ],
+ status: 200,
+ },
+ type: 'receiveArtifactsSuccess',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
+ });
+
+ it('dispatches requestArtifacts and receiveArtifactsError ', done => {
+ testAction(
+ fetchArtifacts,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestArtifacts',
+ },
+ {
+ type: 'receiveArtifactsError',
+ },
+ ],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('receiveArtifactsSuccess', () => {
+ it('should commit RECEIVE_ARTIFACTS_SUCCESS mutation with 200', done => {
+ testAction(
+ receiveArtifactsSuccess,
+ { data: { summary: {} }, status: 200 },
+ mockedState,
+ [{ type: types.RECEIVE_ARTIFACTS_SUCCESS, payload: { summary: {} } }],
+ [],
+ done,
+ );
+ });
+
+ it('should not commit RECEIVE_ARTIFACTS_SUCCESS mutation with 204', done => {
+ testAction(
+ receiveArtifactsSuccess,
+ { data: { summary: {} }, status: 204 },
+ mockedState,
+ [],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveArtifactsError', () => {
+ it('should commit RECEIVE_ARTIFACTS_ERROR mutation', done => {
+ testAction(
+ receiveArtifactsError,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_ARTIFACTS_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/clipboard_button_spec.js b/spec/javascripts/vue_shared/components/clipboard_button_spec.js
index fd17349d48f..29a76574b89 100644
--- a/spec/javascripts/vue_shared/components/clipboard_button_spec.js
+++ b/spec/javascripts/vue_shared/components/clipboard_button_spec.js
@@ -14,7 +14,7 @@ describe('clipboard button', () => {
beforeEach(() => {
vm = mountComponent(Component, {
text: 'copy me',
- title: 'Copy this value into Clipboard!',
+ title: 'Copy this value',
cssClass: 'btn-danger',
});
});
@@ -26,7 +26,7 @@ describe('clipboard button', () => {
});
it('should have a tooltip with default values', () => {
- expect(vm.$el.getAttribute('data-original-title')).toEqual('Copy this value into Clipboard!');
+ expect(vm.$el.getAttribute('data-original-title')).toEqual('Copy this value');
});
it('should render provided classname', () => {
@@ -39,7 +39,7 @@ describe('clipboard button', () => {
vm = mountComponent(Component, {
text: 'copy me',
gfm: '`path/to/file`',
- title: 'Copy this value into Clipboard!',
+ title: 'Copy this value',
cssClass: 'btn-danger',
});
diff --git a/spec/javascripts/vue_shared/components/gl_modal_spec.js b/spec/javascripts/vue_shared/components/deprecated_modal_2_spec.js
index 19af8b5d2f7..64fb984d9fc 100644
--- a/spec/javascripts/vue_shared/components/gl_modal_spec.js
+++ b/spec/javascripts/vue_shared/components/deprecated_modal_2_spec.js
@@ -1,11 +1,11 @@
import $ from 'jquery';
import Vue from 'vue';
-import GlModal from '~/vue_shared/components/gl_modal.vue';
+import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
-const modalComponent = Vue.extend(GlModal);
+const modalComponent = Vue.extend(DeprecatedModal2);
-describe('GlModal', () => {
+describe('DeprecatedModal2', () => {
let vm;
afterEach(() => {
@@ -153,17 +153,17 @@ describe('GlModal', () => {
let template;
if (slotName) {
template = `
- <gl-modal>
+ <deprecated-modal-2>
<template slot="${slotName}">${slotContent}</template>
- </gl-modal>
+ </deprecated-modal-2>
`;
} else {
- template = `<gl-modal>${slotContent}</gl-modal>`;
+ template = `<deprecated-modal-2>${slotContent}</deprecated-modal-2>`;
}
return Vue.extend({
components: {
- GlModal,
+ DeprecatedModal2,
},
template,
});
diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js
index 6abcac5c0ff..7da69e3fa84 100644
--- a/spec/javascripts/vue_shared/components/file_row_spec.js
+++ b/spec/javascripts/vue_shared/components/file_row_spec.js
@@ -90,19 +90,6 @@ describe('File row component', () => {
expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null);
});
- it('is not rendered for `moved` entries in subfolders', () => {
- createComponent({
- file: {
- path: 't5',
- moved: true,
- tree: [],
- },
- level: 2,
- });
-
- expect(vm.$el.nodeType).not.toEqual(1);
- });
-
describe('new dropdown', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/javascripts/vue_shared/components/icon_spec.js b/spec/javascripts/vue_shared/components/icon_spec.js
index 45eef2ad737..7390798afa8 100644
--- a/spec/javascripts/vue_shared/components/icon_spec.js
+++ b/spec/javascripts/vue_shared/components/icon_spec.js
@@ -12,8 +12,6 @@ describe('Sprite Icon Component', function() {
icon = mountComponent(IconComponent, {
name: 'commit',
size: 32,
- cssClasses: 'extraclasses',
- tabIndex: '0',
});
});
@@ -47,10 +45,8 @@ describe('Sprite Icon Component', function() {
it('should properly render img css', function() {
const { classList } = icon.$el;
const containsSizeClass = classList.contains('s32');
- const containsCustomClass = classList.contains('extraclasses');
expect(containsSizeClass).toBe(true);
- expect(containsCustomClass).toBe(true);
});
it('`name` validator should return false for non existing icons', () => {
@@ -60,9 +56,5 @@ describe('Sprite Icon Component', function() {
it('`name` validator should return false for existing icons', () => {
expect(Icon.props.name.validator('commit')).toBe(true);
});
-
- it('should contain `tabindex` attribute on svg element when `tabIndex` prop is defined', () => {
- expect(icon.$el.getAttribute('tabindex')).toBe('0');
- });
});
});
diff --git a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js
index f8271866ca1..9c2deca585b 100644
--- a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js
+++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js
@@ -4,9 +4,11 @@ import ProjectSelector from '~/vue_shared/components/project_selector/project_se
import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue';
import { GlSearchBoxByType } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
+import { mount, createLocalVue } from '@vue/test-utils';
import { trimText } from 'spec/helpers/text_helper';
+const localVue = createLocalVue();
+
describe('ProjectSelector component', () => {
let wrapper;
let vm;
@@ -22,6 +24,7 @@ describe('ProjectSelector component', () => {
jasmine.clock().install();
wrapper = mount(Vue.extend(ProjectSelector), {
+ localVue,
propsData: {
projectSearchResults: searchResults,
selectedProjects: selected,
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 8f662c71c7a..5dee11b3810 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import Dropzone from 'dropzone';
import Mousetrap from 'mousetrap';
import ZenMode from '~/zen_mode';
+import initNotes from '~/init_notes';
describe('ZenMode', () => {
let zen;
@@ -28,6 +29,7 @@ describe('ZenMode', () => {
beforeEach(() => {
loadFixtures(fixtureName);
+ initNotes();
dropzoneForElementSpy = spyOn(Dropzone, 'forElement').and.callFake(() => ({
enable: () => true,