summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2018-07-19 12:49:44 +0100
committerFilipa Lacerda <filipa@gitlab.com>2018-07-20 16:19:16 +0100
commit920b74f519091000dc73a3be02c472ea226aa451 (patch)
tree0836359fd5e7bb20184a06ed92d639b5d72412b9
parentebebf4042bbcdcc32d5aa65b0da470ddc52f0f8e (diff)
downloadgitlab-ce-920b74f519091000dc73a3be02c472ea226aa451.tar.gz
Adds Vuex store for reports section in MR widget
-rw-r--r--app/assets/javascripts/reports/store/actions.js68
-rw-r--r--app/assets/javascripts/reports/store/index.js13
-rw-r--r--app/assets/javascripts/reports/store/mutation_types.js5
-rw-r--r--app/assets/javascripts/reports/store/mutations.js28
-rw-r--r--app/assets/javascripts/reports/store/state.js28
-rw-r--r--changelogs/unreleased/45318-vuex-store.yml5
-rw-r--r--spec/javascripts/reports/store/actions_spec.js149
-rw-r--r--spec/javascripts/reports/store/mutations_spec.js105
8 files changed, 401 insertions, 0 deletions
diff --git a/app/assets/javascripts/reports/store/actions.js b/app/assets/javascripts/reports/store/actions.js
new file mode 100644
index 00000000000..72069489d20
--- /dev/null
+++ b/app/assets/javascripts/reports/store/actions.js
@@ -0,0 +1,68 @@
+import Visibility from 'visibilityjs';
+import axios from '../../lib/utils/axios_utils';
+import Poll from '../../lib/utils/poll';
+import * as types from './mutation_types';
+
+export const setEndpoint = ({ commit }, endpoint) => commit(types.SET_ENDPOINT, endpoint);
+
+export const requestReports = ({ commit }) => commit(types.REQUEST_REPORTS);
+
+let eTagPoll;
+
+export const clearEtagPoll = () => {
+ eTagPoll = null;
+};
+
+export const stopPolling = () => {
+ if (eTagPoll) eTagPoll.stop();
+};
+export const restartPolling = () => {
+ if (eTagPoll) eTagPoll.restart();
+};
+
+/**
+ * We need to poll the reports endpoint while they are being parsed in the Backend.
+ * This can take up to one minute.
+ *
+ * Poll.js will handle etag response.
+ * While http status code is 204, it means it's parsing, and we'll keep polling
+ * When http status code is 200, it means parsing is done, we can show the results & stop polling
+ * When http status code is 500, it means parsins went wrong and we stop polling
+ */
+export const fetchReports = ({ state, dispatch }) => {
+ dispatch('requestReports');
+
+ eTagPoll = new Poll({
+ resource: {
+ getReports(endpoint) {
+ return axios.get(endpoint);
+ },
+ },
+ data: state.endpoint,
+ method: 'getReports',
+ successCallback: ({ data }) => dispatch('receiveReportsSuccess', data),
+ errorCallback: () => dispatch('receiveReportsError'),
+ });
+
+ if (!Visibility.hidden()) {
+ eTagPoll.makeRequest();
+ }
+
+ Visibility.change(() => {
+ if (!Visibility.hidden()) {
+ dispatch('restartPolling');
+ } else {
+ dispatch('stopPolling');
+ }
+ });
+};
+
+export const receiveReportsSuccess = ({ commit }, response) =>
+ commit(types.RECEIVE_REPORTS_SUCCESS, response);
+
+export const receiveReportsError = ({ commit }) => {
+ commit(types.RECEIVE_REPORTS_ERROR);
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/reports/store/index.js b/app/assets/javascripts/reports/store/index.js
new file mode 100644
index 00000000000..af4f9688fb4
--- /dev/null
+++ b/app/assets/javascripts/reports/store/index.js
@@ -0,0 +1,13 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import mutations from './mutations';
+import state from './state';
+
+Vue.use(Vuex);
+
+export default () => new Vuex.Store({
+ actions,
+ mutations,
+ state: state(),
+});
diff --git a/app/assets/javascripts/reports/store/mutation_types.js b/app/assets/javascripts/reports/store/mutation_types.js
new file mode 100644
index 00000000000..77722974c45
--- /dev/null
+++ b/app/assets/javascripts/reports/store/mutation_types.js
@@ -0,0 +1,5 @@
+export const SET_ENDPOINT = 'SET_ENDPOINT';
+
+export const REQUEST_REPORTS = 'REQUEST_REPORTS';
+export const RECEIVE_REPORTS_SUCCESS = 'RECEIVE_REPORTS_SUCCESS';
+export const RECEIVE_REPORTS_ERROR = 'RECEIVE_REPORTS_ERROR';
diff --git a/app/assets/javascripts/reports/store/mutations.js b/app/assets/javascripts/reports/store/mutations.js
new file mode 100644
index 00000000000..9487b8d073a
--- /dev/null
+++ b/app/assets/javascripts/reports/store/mutations.js
@@ -0,0 +1,28 @@
+/* eslint-disable no-param-reassign */
+import Vue from 'vue';
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_ENDPOINT](state, endpoint) {
+ state.endpoint = endpoint;
+ },
+ [types.REQUEST_REPORTS](state) {
+ state.isLoading = true;
+ },
+ [types.RECEIVE_REPORTS_SUCCESS](state, response) {
+
+ state.isLoading = false;
+ state.hasError = false;
+
+ Vue.set(state.summary, 'total', response.summary.total);
+ Vue.set(state.summary, 'resolved', response.summary.resolved);
+ Vue.set(state.summary, 'failed', response.summary.failed);
+
+ state.reports = response.suites;
+
+ },
+ [types.RECEIVE_REPORTS_ERROR](state) {
+ state.isLoading = false;
+ state.hasError = true;
+ },
+};
diff --git a/app/assets/javascripts/reports/store/state.js b/app/assets/javascripts/reports/store/state.js
new file mode 100644
index 00000000000..d4d50c1beab
--- /dev/null
+++ b/app/assets/javascripts/reports/store/state.js
@@ -0,0 +1,28 @@
+export default () => ({
+ endpoint: null,
+
+ isLoading: false,
+ hasError: false,
+
+ summary: {
+ total: 0,
+ resolved: 0,
+ failed: 0,
+ },
+
+ /**
+ * Each report will have the following format:
+ * {
+ * name: {String},
+ * summary: {
+ * total: {Number},
+ * resolved: {Number},
+ * failed: {Number},
+ * },
+ * newFailures: {Array.<Object>},
+ * resolvedFailures: {Array.<Object>},
+ * existingFailures: {Array.<Object>},
+ * }
+ */
+ reports: [],
+});
diff --git a/changelogs/unreleased/45318-vuex-store.yml b/changelogs/unreleased/45318-vuex-store.yml
new file mode 100644
index 00000000000..5ea89034bce
--- /dev/null
+++ b/changelogs/unreleased/45318-vuex-store.yml
@@ -0,0 +1,5 @@
+---
+title: Adds Vuex store for reports section in MR widget
+merge_request: 20709
+author:
+type: added
diff --git a/spec/javascripts/reports/store/actions_spec.js b/spec/javascripts/reports/store/actions_spec.js
new file mode 100644
index 00000000000..8bbb6f06182
--- /dev/null
+++ b/spec/javascripts/reports/store/actions_spec.js
@@ -0,0 +1,149 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import {
+ setEndpoint,
+ requestReports,
+ fetchReports,
+ stopPolling,
+ clearEtagPoll,
+ receiveReportsSuccess,
+ receiveReportsError,
+} from '~/reports/store/actions';
+import state from '~/reports/store/state';
+import * as types from '~/reports/store/mutation_types';
+import testAction from 'spec/helpers/vuex_action_helper';
+
+describe('Reports 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('requestReports', () => {
+ it('should commit REQUEST_REPORTS mutation', done => {
+ testAction(requestReports, null, mockedState, [{ type: types.REQUEST_REPORTS }], [], done);
+ });
+ });
+
+ describe('fetchReports', () => {
+ let mock;
+
+ beforeEach(() => {
+ mockedState.endpoint = 'endpoint.json';
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ stopPolling();
+ clearEtagPoll();
+ });
+
+ describe('success', () => {
+ it('dispatches requestReports and receiveReportsSuccess ', done => {
+ mock.onGet('endpoint.json').reply(200, { summary: {}, suites: [{ name: 'rspec' }] });
+
+ testAction(
+ fetchReports,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestReports',
+ },
+ {
+ type: 'receiveReportsSuccess',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(mockedState.endpoint).reply(500);
+ });
+
+ it('dispatches requestReports and receiveReportsError ', done => {
+ testAction(
+ fetchReports,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestReports',
+ },
+ {
+ type: 'receiveReportsError',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('no content', () => {
+ beforeEach(() => {
+ mock.onGet(mockedState.endpoint).reply(200);
+ });
+
+ it('dispatches requestReports and keeps polling ', done => {
+ testAction(
+ fetchReports,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestReports',
+ },
+ ],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('receiveReportsSuccess', () => {
+ it('should commit RECEIVE_REPORTS_SUCCESS mutation', done => {
+ testAction(
+ receiveReportsSuccess,
+ { summary: {} },
+ mockedState,
+ [{ type: types.RECEIVE_REPORTS_SUCCESS, payload: { summary: {} } }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveReportsError', () => {
+ it('should commit RECEIVE_REPORTS_ERROR mutation', done => {
+ testAction(
+ receiveReportsError,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_REPORTS_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/reports/store/mutations_spec.js b/spec/javascripts/reports/store/mutations_spec.js
new file mode 100644
index 00000000000..90d2203cc66
--- /dev/null
+++ b/spec/javascripts/reports/store/mutations_spec.js
@@ -0,0 +1,105 @@
+import state from '~/reports/store/state';
+import mutations from '~/reports/store/mutations';
+import * as types from '~/reports/store/mutation_types';
+
+describe('Reports Store Mutations', () => {
+ let stateCopy;
+
+ beforeEach(() => {
+ stateCopy = state();
+ });
+
+ describe('SET_ENDPOINT', () => {
+ it('should set endpoint', () => {
+ mutations[types.SET_ENDPOINT](stateCopy, 'endpoint.json');
+ expect(stateCopy.endpoint).toEqual('endpoint.json');
+ });
+ });
+
+ describe('REQUEST_REPORTS', () => {
+ it('should set isLoading to true', () => {
+ mutations[types.REQUEST_REPORTS](stateCopy);
+ expect(stateCopy.isLoading).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_REPORTS_SUCCESS', () => {
+ const mockedResponse = {
+ summary: {
+ total: 14,
+ resolved: 0,
+ failed: 7,
+ },
+ suites: [
+ {
+ name: 'build:linux',
+ summary: {
+ total: 2,
+ resolved: 0,
+ failed: 1,
+ },
+ new_failures: [
+ {
+ name: 'StringHelper#concatenate when a is git and b is lab returns summary',
+ execution_time: 0.0092435,
+ system_output:
+ 'Failure/Error: is_expected.to eq(\'gitlab\')',
+ },
+ ],
+ resolved_failures: [
+ {
+ name: 'StringHelper#concatenate when a is git and b is lab returns summary',
+ execution_time: 0.009235,
+ system_output:
+ 'Failure/Error: is_expected.to eq(\'gitlab\')',
+ },
+ ],
+ existing_failures: [
+ {
+ name: 'StringHelper#concatenate when a is git and b is lab returns summary',
+ execution_time: 1232.08,
+ system_output:
+ 'Failure/Error: is_expected.to eq(\'gitlab\')',
+ },
+ ],
+ },
+ ],
+ };
+
+ beforeEach(() => {
+ mutations[types.RECEIVE_REPORTS_SUCCESS](stateCopy, mockedResponse);
+ });
+
+ it('should reset isLoading', () => {
+ expect(stateCopy.isLoading).toEqual(false);
+ });
+
+ it('should reset hasError', () => {
+ expect(stateCopy.hasError).toEqual(false);
+ });
+
+ it('should set summary counts', () => {
+ expect(stateCopy.summary.total).toEqual(mockedResponse.summary.total);
+ expect(stateCopy.summary.resolved).toEqual(mockedResponse.summary.resolved);
+ expect(stateCopy.summary.failed).toEqual(mockedResponse.summary.failed);
+ });
+
+ it('should set reports', () => {
+ expect(stateCopy.reports).toEqual(mockedResponse.suites);
+ });
+ });
+
+ describe('RECEIVE_REPORTS_ERROR', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_REPORTS_ERROR](stateCopy);
+ });
+ it('should reset isLoading', () => {
+ expect(stateCopy.isLoading).toEqual(false);
+ });
+
+ it('should set hasError to true', () => {
+ expect(stateCopy.hasError).toEqual(true);
+ });
+
+ });
+});