From 7650677d3d832f9d65c8d38a2485ca60b97731c4 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Fri, 5 Apr 2019 02:15:56 +0200 Subject: Rewrite related MRs widget with Vue This MR rewrites existing Related Merge Requests widget with Vue with reusing shared Related Issues components --- app/assets/javascripts/issue.js | 14 --- app/assets/javascripts/issue_show/index.js | 7 +- .../javascripts/issue_show/utils/parse_data.js | 15 +++ .../javascripts/pages/projects/issues/show.js | 2 + .../components/related_merge_requests.vue | 121 +++++++++++++++++++++ .../javascripts/related_merge_requests/index.js | 24 ++++ .../related_merge_requests/store/actions.js | 37 +++++++ .../related_merge_requests/store/index.js | 14 +++ .../related_merge_requests/store/mutation_types.js | 4 + .../related_merge_requests/store/mutations.js | 19 ++++ .../related_merge_requests/store/state.js | 7 ++ .../components/issue/related_issuable_item.vue | 21 ++-- .../vue_shared/mixins/related_issuable_mixin.js | 62 +++++++++++ app/helpers/issuables_helper.rb | 2 + app/views/projects/issues/_closed_by_box.html.haml | 4 - .../projects/issues/_merge_requests.html.haml | 36 ------ app/views/projects/issues/show.html.haml | 5 +- .../_acet-related-mrs-widget-rewrite.yml | 5 + locale/gitlab.pot | 14 +++ .../internal_references_spec.rb | 2 +- spec/javascripts/fixtures/issues.rb | 58 ++++++++++ .../components/related_merge_requests_spec.js | 89 +++++++++++++++ .../related_merge_requests/store/actions_spec.js | 110 +++++++++++++++++++ .../related_merge_requests/store/mutations_spec.js | 49 +++++++++ 24 files changed, 651 insertions(+), 70 deletions(-) create mode 100644 app/assets/javascripts/issue_show/utils/parse_data.js create mode 100644 app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue create mode 100644 app/assets/javascripts/related_merge_requests/index.js create mode 100644 app/assets/javascripts/related_merge_requests/store/actions.js create mode 100644 app/assets/javascripts/related_merge_requests/store/index.js create mode 100644 app/assets/javascripts/related_merge_requests/store/mutation_types.js create mode 100644 app/assets/javascripts/related_merge_requests/store/mutations.js create mode 100644 app/assets/javascripts/related_merge_requests/store/state.js delete mode 100644 app/views/projects/issues/_closed_by_box.html.haml delete mode 100644 app/views/projects/issues/_merge_requests.html.haml create mode 100644 changelogs/unreleased/_acet-related-mrs-widget-rewrite.yml create mode 100644 spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js create mode 100644 spec/javascripts/related_merge_requests/store/actions_spec.js create mode 100644 spec/javascripts/related_merge_requests/store/mutations_spec.js diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index b3508f36cf9..cd1afb6ba83 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -15,7 +15,6 @@ export default class Issue { Issue.$btnNewBranch = $('#new-branch'); Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap'); - Issue.initMergeRequests(); if (document.querySelector('#related-branches')) { Issue.initRelatedBranches(); } @@ -143,19 +142,6 @@ export default class Issue { } } - static initMergeRequests() { - var $container; - $container = $('#merge-requests'); - return axios - .get($container.data('url')) - .then(({ data }) => { - if ('html' in data) { - $container.html(data.html); - } - }) - .catch(() => flash('Failed to load referenced merge requests')); - } - static initRelatedBranches() { var $container; $container = $('#related-branches'); diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js index d08e8ba0c4b..529b6386221 100644 --- a/app/assets/javascripts/issue_show/index.js +++ b/app/assets/javascripts/issue_show/index.js @@ -1,12 +1,9 @@ import Vue from 'vue'; -import sanitize from 'sanitize-html'; import issuableApp from './components/app.vue'; +import { parseIssuableData } from './utils/parse_data'; import '../vue_shared/vue_resource_interceptor'; export default function initIssueableApp() { - const initialDataEl = document.getElementById('js-issuable-app-initial-data'); - const props = JSON.parse(sanitize(initialDataEl.textContent).replace(/"/g, '"')); - return new Vue({ el: document.getElementById('js-issuable-app'), components: { @@ -14,7 +11,7 @@ export default function initIssueableApp() { }, render(createElement) { return createElement('issuable-app', { - props, + props: parseIssuableData(), }); }, }); diff --git a/app/assets/javascripts/issue_show/utils/parse_data.js b/app/assets/javascripts/issue_show/utils/parse_data.js new file mode 100644 index 00000000000..05e384adad3 --- /dev/null +++ b/app/assets/javascripts/issue_show/utils/parse_data.js @@ -0,0 +1,15 @@ +import sanitize from 'sanitize-html'; + +export const parseIssuableData = () => { + try { + const initialDataEl = document.getElementById('js-issuable-app-initial-data'); + + return JSON.parse(sanitize(initialDataEl.textContent).replace(/"/g, '"')); + } catch (e) { + console.error(e); // eslint-disable-line no-console + + return {}; + } +}; + +export default {}; diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js index 8987c8e3f47..0447d1f79fb 100644 --- a/app/assets/javascripts/pages/projects/issues/show.js +++ b/app/assets/javascripts/pages/projects/issues/show.js @@ -4,9 +4,11 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable'; import ZenMode from '~/zen_mode'; import '~/notes/index'; import initIssueableApp from '~/issue_show'; +import initRelatedMergeRequestsApp from '~/related_merge_requests'; export default function() { initIssueableApp(); + initRelatedMergeRequestsApp(); new Issue(); // eslint-disable-line no-new new ShortcutsIssuable(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue new file mode 100644 index 00000000000..52d4b75a3a1 --- /dev/null +++ b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue @@ -0,0 +1,121 @@ + + + diff --git a/app/assets/javascripts/related_merge_requests/index.js b/app/assets/javascripts/related_merge_requests/index.js new file mode 100644 index 00000000000..092ff1df00f --- /dev/null +++ b/app/assets/javascripts/related_merge_requests/index.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import RelatedMergeRequests from './components/related_merge_requests.vue'; +import createStore from './store'; + +export default function initRelatedMergeRequests() { + const relatedMergeRequestsElement = document.querySelector('#js-related-merge-requests'); + + if (relatedMergeRequestsElement) { + const { endpoint, projectPath, projectNamespace } = relatedMergeRequestsElement.dataset; + + // eslint-disable-next-line no-new + new Vue({ + el: relatedMergeRequestsElement, + components: { + RelatedMergeRequests, + }, + store: createStore(), + render: createElement => + createElement('related-merge-requests', { + props: { endpoint, projectNamespace, projectPath }, + }), + }); + } +} diff --git a/app/assets/javascripts/related_merge_requests/store/actions.js b/app/assets/javascripts/related_merge_requests/store/actions.js new file mode 100644 index 00000000000..69abeaaf7db --- /dev/null +++ b/app/assets/javascripts/related_merge_requests/store/actions.js @@ -0,0 +1,37 @@ +import axios from '~/lib/utils/axios_utils'; +import createFlash from '~/flash'; +import { s__ } from '~/locale'; +import { normalizeHeaders } from '~/lib/utils/common_utils'; +import * as types from './mutation_types'; + +const REQUEST_PAGE_COUNT = 100; + +export const setInitialState = ({ commit }, props) => { + commit(types.SET_INITIAL_STATE, props); +}; + +export const requestData = ({ commit }) => commit(types.REQUEST_DATA); + +export const receiveDataSuccess = ({ commit }, data) => commit(types.RECEIVE_DATA_SUCCESS, data); + +export const receiveDataError = ({ commit }) => commit(types.RECEIVE_DATA_ERROR); + +export const fetchMergeRequests = ({ state, dispatch }) => { + dispatch('requestData'); + + return axios + .get(`${state.apiEndpoint}?per_page=${REQUEST_PAGE_COUNT}`) + .then(res => { + const { headers, data } = res; + const total = Number(normalizeHeaders(headers)['X-TOTAL']) || 0; + + dispatch('receiveDataSuccess', { data, total }); + }) + .catch(() => { + dispatch('receiveDataError'); + createFlash(s__('Something went wrong while fetching related merge requests.')); + }); +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/related_merge_requests/store/index.js b/app/assets/javascripts/related_merge_requests/store/index.js new file mode 100644 index 00000000000..dcb70c22bcb --- /dev/null +++ b/app/assets/javascripts/related_merge_requests/store/index.js @@ -0,0 +1,14 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import createState from './state'; +import * as actions from './actions'; +import mutations from './mutations'; + +Vue.use(Vuex); + +export default () => + new Vuex.Store({ + state: createState(), + actions, + mutations, + }); diff --git a/app/assets/javascripts/related_merge_requests/store/mutation_types.js b/app/assets/javascripts/related_merge_requests/store/mutation_types.js new file mode 100644 index 00000000000..31d4fe032e1 --- /dev/null +++ b/app/assets/javascripts/related_merge_requests/store/mutation_types.js @@ -0,0 +1,4 @@ +export const SET_INITIAL_STATE = 'SET_INITIAL_STATE'; +export const REQUEST_DATA = 'REQUEST_DATA'; +export const RECEIVE_DATA_SUCCESS = 'RECEIVE_DATA_SUCCESS'; +export const RECEIVE_DATA_ERROR = 'RECEIVE_DATA_ERROR'; diff --git a/app/assets/javascripts/related_merge_requests/store/mutations.js b/app/assets/javascripts/related_merge_requests/store/mutations.js new file mode 100644 index 00000000000..11ca28a5fb9 --- /dev/null +++ b/app/assets/javascripts/related_merge_requests/store/mutations.js @@ -0,0 +1,19 @@ +import * as types from './mutation_types'; + +export default { + [types.SET_INITIAL_STATE](state, { apiEndpoint }) { + state.apiEndpoint = apiEndpoint; + }, + [types.REQUEST_DATA](state) { + state.isFetchingMergeRequests = true; + }, + [types.RECEIVE_DATA_SUCCESS](state, { data, total }) { + state.isFetchingMergeRequests = false; + state.mergeRequests = data; + state.totalCount = total; + }, + [types.RECEIVE_DATA_ERROR](state) { + state.isFetchingMergeRequests = false; + state.hasErrorFetchingMergeRequests = true; + }, +}; diff --git a/app/assets/javascripts/related_merge_requests/store/state.js b/app/assets/javascripts/related_merge_requests/store/state.js new file mode 100644 index 00000000000..bc3468a025b --- /dev/null +++ b/app/assets/javascripts/related_merge_requests/store/state.js @@ -0,0 +1,7 @@ +export default () => ({ + apiEndpoint: '', + isFetchingMergeRequests: false, + hasErrorFetchingMergeRequests: false, + mergeRequests: [], + totalCount: 0, +}); diff --git a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue index 27cfa8abb24..d4d18614f93 100644 --- a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue +++ b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue @@ -1,15 +1,17 @@