summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2018-02-28 16:51:04 +0000
committerFilipa Lacerda <filipa@gitlab.com>2018-02-28 16:51:04 +0000
commit15c8fafaef2a7dba24f5de45346067ccd4694c15 (patch)
treec650ade5c009268408052dca2a7872ed56452977
parent4371f845649deaf6bf31f0a675b33f1d58f64de4 (diff)
downloadgitlab-ce-38587-pipeline-empty-state.tar.gz
First iteration of cleaning up Pipelines code [ci skip]38587-pipeline-empty-state
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js2
-rw-r--r--app/assets/javascripts/pipelines/stores/pipelines/actions.js218
-rw-r--r--app/assets/javascripts/pipelines/stores/pipelines/getters.js24
-rw-r--r--app/assets/javascripts/pipelines/stores/pipelines/index.js16
-rw-r--r--app/assets/javascripts/pipelines/stores/pipelines/mutation_types.js22
-rw-r--r--app/assets/javascripts/pipelines/stores/pipelines/mutations.js102
-rw-r--r--app/assets/javascripts/pipelines/stores/pipelines/state.js36
7 files changed, 419 insertions, 1 deletions
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index e741789fbb6..5146d53cd50 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -269,7 +269,7 @@ export const normalizeCRLFHeaders = (headers) => {
* @param {Object} paginationInformation
* @returns {Object}
*/
-export const parseIntPagination = paginationInformation => ({
+export const parseIntPagination = (paginationInformation = {}) => ({
perPage: parseInt(paginationInformation['X-PER-PAGE'], 10),
page: parseInt(paginationInformation['X-PAGE'], 10),
total: parseInt(paginationInformation['X-TOTAL'], 10),
diff --git a/app/assets/javascripts/pipelines/stores/pipelines/actions.js b/app/assets/javascripts/pipelines/stores/pipelines/actions.js
new file mode 100644
index 00000000000..93bd664c14b
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/pipelines/actions.js
@@ -0,0 +1,218 @@
+import _ from 'underscore';
+import axios from '../../../lib/utils/axios_utils';
+import createFlash from '../../../flash';
+import { __ } from '../../../locale';
+import { parseQueryStringIntoObject } from '../../../lib/utils/common_utils';
+import * as types from './mutation_types';
+
+/**
+ * SYNC ACTIONS - BASE DATA
+ */
+
+export const setEndpoint = ({ commit }, endpoint) => {
+ commit(types.SET_ENDPOINT, endpoint);
+};
+
+export const setRailsData = ({ commit }, dataset) => {
+ commit(types.SET_PATHS, dataset);
+ commit(types.SET_PERMISSIONS, dataset);
+};
+
+/**
+ * Both the store and the components are shared between two apps.
+ * 1. Main app is rendered in /pipelines route
+ * 2. Child App is rendered in the following pages:
+ * 1. merge_requests/pipelines
+ * 2. commits/pipelines
+ * 3. new merge request/pipelines (create route)
+ * @param {Object}
+ * @param {String} type main || child
+ */
+export const setViewType = ({ commit }, type) => {
+ commit(types.SET_VIEW_TYPE, type);
+};
+
+export const setHasCI = ({ commit }, value) => {
+ commit(types.SET_HAS_CI, value);
+};
+
+/**
+ * SYNC ACTIONS - DYNAMIC DATA
+ */
+
+/**
+ * Commits a mutation to store the pipelines.
+ * Detached to allow being shared between two applications:
+ * - Main Pipeline View
+ * - MR & Commits View
+ * @param {Object}
+ */
+export const setPipelines = ({ commit }, pipelines) => {
+ commit(types.SET_PIPELINES, pipelines);
+};
+
+/**
+ * Commits a mutation to store the counts used in the tabs in the Main Pipelines view.
+ * @param {Object}
+ */
+export const setCount = ({ commit }, count) => {
+ commit(types.SET_COUNT, count);
+};
+
+/**
+ * Commits a mutation to store the pagination used in the Main Pipelines view.
+ * @param {Object}
+ */
+export const setPagination = ({ commit }, pagination = {}) => {
+ commit(types.SET_PAGINATION, pagination);
+};
+
+export const showLoading = ({ commit }) => {
+ commit(types.SHOW_LOADING);
+};
+
+export const hideLoading = ({ commit }) => {
+ commit(types.HIDE_LOADING);
+};
+
+export const toggleError = ({ commit }, value) => {
+ commit(types.SET_HAS_ERROR, value);
+};
+
+export const toggleIsMakingRequest = ({ commit }, value) => {
+ commit(types.SET_IS_MAKING_REQUEST, value);
+};
+
+export const toggleHasMadeRequest = ({ commit }, value) => {
+ commit(types.SET_HAS_MADE_REQUEST, value);
+};
+
+export const toggleShouldUpdateGraphDropdown = ({ commit }, value) => {
+ commit(types.SET_SHOULD_UPDATE_GRAPH_DROPDOWN, value);
+};
+
+/**
+ * Used to handle state between pagination, tabs and URL
+ *
+ * @param {Object}
+ * @param {String} scope
+ */
+export const setScope = ({ commit }, scope) => {
+ commit(types.SET_SCOPE, scope);
+};
+
+/**
+ * Used to handle state between pagination, tabs and URL
+ *
+ * @param {Object}
+ * @param {String} page
+ */
+export const setPage = ({ commit }, page) => {
+ commit(types.SET_SCOPE, page);
+};
+
+/**
+ * Used to update the polling class with the correct state
+ * between pagination, tabs and URL
+ *
+ * @param {Object}
+ * @param {Object} requestData
+ */
+export const setRequestData = ({ commit }, requestData) => {
+ commit(types.SET_REQUEST_DATA, requestData);
+};
+
+/**
+ * ASYNC ACTIONS
+ */
+
+ /*
+**
+* axios callbacks are detached from the main action to allow to work with the polling function.
+* Actions will be dispacted in the polling callback.
+*
+* Actions that set data are divided to allow the usage in two different apps.
+* They should be the same in the future once the MR and Commits pipelines table allow pagination.
+*/
+
+/**
+* Fetched the given endpoint and parameters.
+* Used in polling method and in the refresh pipelines method.
+*
+* @returns {Promise}
+*/
+export const getPipelines = ({ state }) => axios
+ .get(state.endpoint, { scope: state.scope, page: state.page });
+
+/**
+* Commits mutations for the common data shared between two applications:
+* - Main Pipeline View
+* - MR & Commits View
+* @param {Object}
+*/
+export const successCallback = ({ dispatch, commit, state }, response) => {
+ dispatch('hideLoading');
+ dispatch('shouldUpdateGraphDropdown', true);
+ dispatch('toggleHasMadeRequest', true);
+
+ if (state.type === 'main') {
+ if (_.isEqual(parseQueryStringIntoObject(response.url.split('?')[1]), state.requestData)) {
+ dispatch('setPagination', response.headers);
+ dispatch('setCount', response.data.count);
+ commit(types.SET_PIPELINES, response.data.pipelines);
+ }
+ } else if (state.type === 'child') {
+ // depending of the endpoint the response can either bring a `pipelines` key or not.
+ const pipelines = response.data.pipelines || response.data;
+ commit(types.SET_PIPELINES, pipelines);
+ dispatch('emitEventUpdateCount', response);
+ }
+};
+
+export const errorCallback = ({ dispatch }) => {
+ dispatch('hideLoading');
+ dispatch('shouldUpdateGraphDropdown', true);
+ dispatch('toggleError', true);
+};
+
+// TODO HANDLE THIS!
+export const emitEventUpdateCount = (store, response) => {
+ const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
+ detail: {
+ pipelines: response,
+ },
+ });
+
+ // notifiy to update the count in tabs
+ // if (this.$el.parentElement) {
+ // this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
+ // }
+};
+
+export const stopPipeline = ({ dispatch, state, commit }, endpoint) => axios.post(`${endpoint}.json`)
+ .then(() => {
+ dispatch('refreshPipeline');
+ })
+ .catch(() => {
+ createFlash(__('An error occurred while trying to stop the pipeline. Please try again.'));
+ });
+
+export const retryPipeline = ({ dispatch, state, commit }, endpoint) => axios.post(`${endpoint}.json`)
+ .then(() => {
+ dispatch('refreshPipeline');
+ })
+ .catch(() => {
+ createFlash(__('An error occurred while trying to retry the pipeline. Please try again.'));
+ });
+
+export const refreshPipeline = ({ state, dispatch }) => {
+ if (!state.isMakingRequest) {
+ dispatch('showLoading');
+ dispatch('getPipelines')
+ .then(response => dispatch('successCallback', response))
+ .catch(() => {
+ dispatch('errorCallback');
+ createFlash(__('An error occurred while fetching the pipelines. Please try again.'));
+ });
+ }
+};
diff --git a/app/assets/javascripts/pipelines/stores/pipelines/getters.js b/app/assets/javascripts/pipelines/stores/pipelines/getters.js
new file mode 100644
index 00000000000..5db4baecfc8
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/pipelines/getters.js
@@ -0,0 +1,24 @@
+export const shouldRenderErrorState = state => state.hasError && !state.isLoading;
+
+export const shouldRenderEmptyState = (state) => {
+ if (state.viewType === 'main') {
+ return !state.isLoading &&
+ !state.hasError &&
+ !state.pipelines.length &&
+ state.scope !== 'all' &&
+ state.scope !== null;
+ }
+
+ return !state.pipelines.length &&
+ !state.isLoading &&
+ state.hasMadeRequest &&
+ !state.hasError;
+};
+
+export const shouldRenderPipelinesTable = state => !state.isLoading &&
+ !state.hasError &&
+ state.pipelines.length;
+
+export const shouldRenderRunPipelineButton = state => state.permissions.canCreatePipeline && state.hasCI;
+
+export const shouldRenderClearCacheButton = state => state.permissions. \ No newline at end of file
diff --git a/app/assets/javascripts/pipelines/stores/pipelines/index.js b/app/assets/javascripts/pipelines/stores/pipelines/index.js
new file mode 100644
index 00000000000..aef8d91dfbc
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/pipelines/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import * as mutations from './mutations';
+import * as getters from './getters';
+import state from './state';
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+ actions,
+ getters,
+ mutations,
+ state,
+});
+
diff --git a/app/assets/javascripts/pipelines/stores/pipelines/mutation_types.js b/app/assets/javascripts/pipelines/stores/pipelines/mutation_types.js
new file mode 100644
index 00000000000..5da8602cadf
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/pipelines/mutation_types.js
@@ -0,0 +1,22 @@
+export const SET_ENDPOINT = 'SET_ENDPOINT';
+export const SET_PERMISSIONS = 'SET_RAILS_DATA';
+export const SET_PATHS = 'SET_RAILS_DATA';
+export const SET_VIEW_TYPE = 'SET_VIEW_TYPE';
+export const SET_HAS_CI = 'SET_VIEW_TYPE';
+
+export const SHOW_LOADING = 'SHOW_LOADING';
+export const HIDE_LOADING = 'HIDE_LOADING';
+
+export const SET_HAS_ERROR = 'SET_HAS_ERROR';
+export const SET_IS_MAKING_REQUEST = 'SET_IS_MAKING_REQUEST';
+export const SET_HAS_MADE_REQUEST = 'SET_HAS_MADE_REQUEST';
+export const SET_SHOULD_UPDATE_GRAPH_DROPDOWN = 'SET_SHOULD_UPDATE_GRAPH_DROPDOWN';
+
+export const SET_SCOPE = 'SET_SCOPE';
+export const SET_PAGE = 'SET_PAGE';
+export const SET_REQUEST_DATA = 'SET_REQUEST_DATA';
+
+export const SET_PIPELINES = 'SET_PIPELINES';
+export const SET_PAGINATION = 'SET_PAGINATION';
+export const SET_COUNT = 'SET_COUNT';
+
diff --git a/app/assets/javascripts/pipelines/stores/pipelines/mutations.js b/app/assets/javascripts/pipelines/stores/pipelines/mutations.js
new file mode 100644
index 00000000000..51b3596ec58
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/pipelines/mutations.js
@@ -0,0 +1,102 @@
+import * as types from './mutation_types';
+import {
+ parseIntPagination,
+ normalizeHeaders,
+ convertPermissionToBoolean,
+} from '../../../lib/utils/common_utils';
+
+export default {
+ /**
+ * Because it's used in several different routes the endpoint may vary:
+ * 1. In the new merge request view the endpoint won't include `.json`
+ * 1. In the MR view, Commits view and Pipelines view it will include `.json`
+ */
+ [types.SET_ENDPOINT](state, endpoint) {
+ let parsedEndpoint;
+
+ if (endpoint.indexOf('.json') === -1) {
+ parsedEndpoint = `${endpoint}.json`;
+ } else {
+ parsedEndpoint = endpoint;
+ }
+
+ Object.assign(state, { endpoint: parsedEndpoint });
+ },
+
+ [types.SET_PERMISSIONS](state, dataset) {
+ Object.assign(state, {
+ canCreatePipeline: convertPermissionToBoolean(dataset.canCreatePipeline),
+ canResetCache: dataset.resetCachePath !== null || dataset.resetCachePath !== undefined,
+ });
+ },
+
+ [types.SET_PATHS](state, dataset) {
+ Object.assign(state, {
+ helpPagePath: dataset.helpPagePath,
+ autoDevopsHelpPath: dataset.autoDevopsHelpPath,
+ newPipelinePath: dataset.newPipelinePath,
+ ciLintPath: dataset.ciLintPath,
+ resetCachePath: dataset.resetCachePath,
+ emptyStateSvgPath: dataset.emptyStateSvgPath,
+ errorStateSvgPath: dataset.errorStateSvgPath,
+ });
+ },
+
+ [types.SET_VIEW_TYPE](state, viewType) {
+ Object.assign(state, { viewType });
+ },
+
+ [types.SET_HAS_CI](state, value) {
+ Object.assign(state, { hasCI: value });
+ },
+
+ [types.SHOW_LOADING](state) {
+ Object.assign(state, { isLoading: true });
+ },
+
+ [types.HIDE_LOADING](state) {
+ Object.assign(state, { isLoading: false });
+ },
+
+ [types.SET_HAS_ERROR](state, value) {
+ Object.assign(state, { hasError: value });
+ },
+
+ [types.SET_IS_MAKING_REQUEST](state, value) {
+ Object.assign(state, { isMakingRequest: value });
+ },
+
+ [types.SET_HAS_MADE_REQUEST](state, value) {
+ Object.assign(state, { hasMadeRequest: value });
+ },
+
+ [types.SET_SHOULD_UPDATE_GRAPH_DROPDOWN](state, value) {
+ Object.assign(state, { updateGraphDropdown: value });
+ },
+
+ [types.SET_SCOPE](state, value) {
+ Object.assign(state, { scope: value });
+ },
+
+ [types.SET_PAGE](state, value) {
+ Object.assign(state, { page: value });
+ },
+
+ [types.SET_REQUEST_DATA](state, value) {
+ Object.assign(state, { requestData: value });
+ },
+
+ [types.SET_PIPELINES](state, pipelines) {
+ Object.assign(state, { pipelines });
+ },
+
+ [types.SET_PAGINATION](state, pagination) {
+ const parsedPagination = parseIntPagination(normalizeHeaders(pagination));
+
+ Object.assign(state, { pagination: parsedPagination });
+ },
+
+ [types.SET_COUNT](state, pipelines) {
+ Object.assign(state, { pipelines });
+ },
+};
diff --git a/app/assets/javascripts/pipelines/stores/pipelines/state.js b/app/assets/javascripts/pipelines/stores/pipelines/state.js
new file mode 100644
index 00000000000..88428222920
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/pipelines/state.js
@@ -0,0 +1,36 @@
+export default {
+ endpoint: null,
+
+ permissions: {
+ canCreatePipeline: false,
+ canResetCache: false,
+ },
+
+ paths: {
+ helpPagePath: null,
+ newPipelinePath: null,
+ ciLintPath: null,
+ emptyStateSvgPath: null,
+ errorStateSvgPath: null,
+ autoDevopsPath: null,
+ resetCachePath: null,
+ },
+
+ viewType: 'main', // 'main || child'
+
+ hasCI: false,
+
+ isLoading: false,
+ hasError: false,
+ isMakingRequest: false,
+ updateGraphDropdown: false,
+ hasMadeRequest: false,
+
+ scope: 'all',
+ page: '1',
+ requestData: {},
+
+ pipelines: [],
+ count: {},
+ pagination: {},
+};