From 57f6be00bcaa20287c696d67128655738d740b8b Mon Sep 17 00:00:00 2001 From: Adriel Santiago Date: Tue, 4 Jun 2019 17:55:52 +0000 Subject: Handle external dashboard form submission Connect frontend UI with backend api for external dashboard link --- .../monitoring/components/dashboard.vue | 7 +- .../components/external_dashboard.vue | 31 +++-- app/assets/javascripts/operation_settings/index.js | 9 +- .../operation_settings/store/actions.js | 38 ++++++ .../javascripts/operation_settings/store/index.js | 16 +++ .../operation_settings/store/mutation_types.js | 3 + .../operation_settings/store/mutations.js | 7 + .../javascripts/operation_settings/store/state.js | 5 + .../operations/_external_dashboard.html.haml | 3 +- .../components/external_dashboard_spec.js | 150 ++++++++++++++------- .../operation_settings/store/mutations_spec.js | 19 +++ spec/javascripts/monitoring/dashboard_spec.js | 4 +- 12 files changed, 222 insertions(+), 70 deletions(-) create mode 100644 app/assets/javascripts/operation_settings/store/actions.js create mode 100644 app/assets/javascripts/operation_settings/store/index.js create mode 100644 app/assets/javascripts/operation_settings/store/mutation_types.js create mode 100644 app/assets/javascripts/operation_settings/store/mutations.js create mode 100644 app/assets/javascripts/operation_settings/store/state.js create mode 100644 spec/frontend/operation_settings/store/mutations_spec.js diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index cc7b5700215..def810e879a 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -38,7 +38,7 @@ export default { GlModalDirective, }, props: { - externalDashboardPath: { + externalDashboardUrl: { type: String, required: false, default: '', @@ -299,10 +299,11 @@ export default { {{ __('View full dashboard') }} diff --git a/app/assets/javascripts/operation_settings/components/external_dashboard.vue b/app/assets/javascripts/operation_settings/components/external_dashboard.vue index 59251f70337..ed518611d0b 100644 --- a/app/assets/javascripts/operation_settings/components/external_dashboard.vue +++ b/app/assets/javascripts/operation_settings/components/external_dashboard.vue @@ -1,4 +1,5 @@ @@ -45,11 +53,12 @@ export default { :description="s__('ExternalMetrics|Enter the URL of the dashboard you want to link to')" > - + {{ __('Save Changes') }} diff --git a/app/assets/javascripts/operation_settings/index.js b/app/assets/javascripts/operation_settings/index.js index 1171f3ece9f..6946578e6d2 100644 --- a/app/assets/javascripts/operation_settings/index.js +++ b/app/assets/javascripts/operation_settings/index.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import store from './store'; import ExternalDashboardForm from './components/external_dashboard.vue'; export default () => { @@ -14,13 +15,9 @@ export default () => { return new Vue({ el, + store: store(el.dataset), render(createElement) { - return createElement(ExternalDashboardForm, { - props: { - ...el.dataset, - expanded: false, - }, - }); + return createElement(ExternalDashboardForm); }, }); }; diff --git a/app/assets/javascripts/operation_settings/store/actions.js b/app/assets/javascripts/operation_settings/store/actions.js new file mode 100644 index 00000000000..ec05b0c76cf --- /dev/null +++ b/app/assets/javascripts/operation_settings/store/actions.js @@ -0,0 +1,38 @@ +import axios from '~/lib/utils/axios_utils'; +import { __ } from '~/locale'; +import createFlash from '~/flash'; +import { refreshCurrentPage } from '~/lib/utils/url_utility'; +import * as mutationTypes from './mutation_types'; + +export const setExternalDashboardUrl = ({ commit }, url) => + commit(mutationTypes.SET_EXTERNAL_DASHBOARD_URL, url); + +export const updateExternalDashboardUrl = ({ state, dispatch }) => + axios + .patch(state.operationsSettingsEndpoint, { + project: { + metrics_setting_attributes: { + external_dashboard_url: state.externalDashboardUrl, + }, + }, + }) + .then(() => dispatch('receiveExternalDashboardUpdateSuccess')) + .catch(error => dispatch('receiveExternalDashboardUpdateError', error)); + +export const receiveExternalDashboardUpdateSuccess = () => { + /** + * The operations_controller currently handles successful requests + * by creating a flash banner messsage to notify the user. + */ + refreshCurrentPage(); +}; + +export const receiveExternalDashboardUpdateError = (_, error) => { + const { response } = error; + const message = response.data && response.data.message ? response.data.message : ''; + + createFlash(`${__('There was an error saving your changes.')} ${message}`, 'alert'); +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/operation_settings/store/index.js b/app/assets/javascripts/operation_settings/store/index.js new file mode 100644 index 00000000000..e96bb1e8aad --- /dev/null +++ b/app/assets/javascripts/operation_settings/store/index.js @@ -0,0 +1,16 @@ +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 const createStore = initialState => + new Vuex.Store({ + state: createState(initialState), + actions, + mutations, + }); + +export default createStore; diff --git a/app/assets/javascripts/operation_settings/store/mutation_types.js b/app/assets/javascripts/operation_settings/store/mutation_types.js new file mode 100644 index 00000000000..237d2b6122f --- /dev/null +++ b/app/assets/javascripts/operation_settings/store/mutation_types.js @@ -0,0 +1,3 @@ +/* eslint-disable import/prefer-default-export */ + +export const SET_EXTERNAL_DASHBOARD_URL = 'SET_EXTERNAL_DASHBOARD_URL'; diff --git a/app/assets/javascripts/operation_settings/store/mutations.js b/app/assets/javascripts/operation_settings/store/mutations.js new file mode 100644 index 00000000000..64bb33bb89f --- /dev/null +++ b/app/assets/javascripts/operation_settings/store/mutations.js @@ -0,0 +1,7 @@ +import * as types from './mutation_types'; + +export default { + [types.SET_EXTERNAL_DASHBOARD_URL](state, url) { + state.externalDashboardUrl = url; + }, +}; diff --git a/app/assets/javascripts/operation_settings/store/state.js b/app/assets/javascripts/operation_settings/store/state.js new file mode 100644 index 00000000000..72167141c48 --- /dev/null +++ b/app/assets/javascripts/operation_settings/store/state.js @@ -0,0 +1,5 @@ +export default (initialState = {}) => ({ + externalDashboardUrl: initialState.externalDashboardUrl || '', + operationsSettingsEndpoint: initialState.operationsSettingsEndpoint, + externalDashboardHelpPagePath: initialState.externalDashboardHelpPagePath, +}); diff --git a/app/views/projects/settings/operations/_external_dashboard.html.haml b/app/views/projects/settings/operations/_external_dashboard.html.haml index f049c35b38d..a124283921d 100644 --- a/app/views/projects/settings/operations/_external_dashboard.html.haml +++ b/app/views/projects/settings/operations/_external_dashboard.html.haml @@ -1,2 +1,3 @@ -.js-operation-settings{ data: { external_dashboard: { path: metrics_external_dashboard_url, +.js-operation-settings{ data: { operations_settings_endpoint: project_settings_operations_path(@project), + external_dashboard: { url: metrics_external_dashboard_url, help_page_path: help_page_path('user/project/operations/link_to_external_dashboard') } } } diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js index 23dc3c3db8c..986aada0b03 100644 --- a/spec/frontend/operation_settings/components/external_dashboard_spec.js +++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js @@ -1,23 +1,48 @@ -import { shallowMount } from '@vue/test-utils'; +import { mount, shallowMount, createLocalVue } from '@vue/test-utils'; import { GlButton, GlLink, GlFormGroup, GlFormInput } from '@gitlab/ui'; import ExternalDashboard from '~/operation_settings/components/external_dashboard.vue'; +import store from '~/operation_settings/store'; +import axios from '~/lib/utils/axios_utils'; +import { refreshCurrentPage } from '~/lib/utils/url_utility'; +import createFlash from '~/flash'; import { TEST_HOST } from 'helpers/test_constants'; +jest.mock('~/lib/utils/axios_utils'); +jest.mock('~/lib/utils/url_utility'); +jest.mock('~/flash'); + describe('operation settings external dashboard component', () => { let wrapper; - const externalDashboardPath = `http://mock-external-domain.com/external/dashboard/path`; + const operationsSettingsEndpoint = `${TEST_HOST}/mock/ops/settings/endpoint`; + const externalDashboardUrl = `http://mock-external-domain.com/external/dashboard/url`; const externalDashboardHelpPagePath = `${TEST_HOST}/help/page/path`; - - beforeEach(() => { - wrapper = shallowMount(ExternalDashboard, { - propsData: { - externalDashboardPath, - externalDashboardHelpPagePath, + const localVue = createLocalVue(); + const mountComponent = (shallow = true) => { + const config = [ + ExternalDashboard, + { + localVue, + store: store({ + operationsSettingsEndpoint, + externalDashboardUrl, + externalDashboardHelpPagePath, + }), }, - }); + ]; + wrapper = shallow ? shallowMount(...config) : mount(...config); + }; + + afterEach(() => { + if (wrapper.destroy) { + wrapper.destroy(); + } + axios.patch.mockReset(); + refreshCurrentPage.mockReset(); + createFlash.mockReset(); }); it('renders header text', () => { + mountComponent(); expect(wrapper.find('.js-section-header').text()).toBe('External Dashboard'); }); @@ -33,6 +58,7 @@ describe('operation settings external dashboard component', () => { let subHeader; beforeEach(() => { + mountComponent(); subHeader = wrapper.find('.js-section-sub-header'); }); @@ -51,57 +77,87 @@ describe('operation settings external dashboard component', () => { }); describe('form', () => { - let form; + describe('input label', () => { + let formGroup; - beforeEach(() => { - form = wrapper.find('form'); - }); + beforeEach(() => { + mountComponent(); + formGroup = wrapper.find(GlFormGroup); + }); - describe('external dashboard url', () => { - describe('input label', () => { - let formGroup; + it('uses label text', () => { + expect(formGroup.attributes().label).toBe('Full dashboard URL'); + }); - beforeEach(() => { - formGroup = form.find(GlFormGroup); - }); + it('uses description text', () => { + expect(formGroup.attributes().description).toBe( + 'Enter the URL of the dashboard you want to link to', + ); + }); + }); - it('uses label text', () => { - expect(formGroup.attributes().label).toBe('Full dashboard URL'); - }); + describe('input field', () => { + let input; - it('uses description text', () => { - expect(formGroup.attributes().description).toBe( - 'Enter the URL of the dashboard you want to link to', - ); - }); + beforeEach(() => { + mountComponent(); + input = wrapper.find(GlFormInput); }); - describe('input field', () => { - let input; - - beforeEach(() => { - input = form.find(GlFormInput); - }); + it('defaults to externalDashboardUrl', () => { + expect(input.attributes().value).toBe(externalDashboardUrl); + }); - it('defaults to externalDashboardPath prop', () => { - expect(input.attributes().value).toBe(externalDashboardPath); - }); + it('uses a placeholder', () => { + expect(input.attributes().placeholder).toBe('https://my-org.gitlab.io/my-dashboards'); + }); + }); - it('uses a placeholder', () => { - expect(input.attributes().placeholder).toBe('https://my-org.gitlab.io/my-dashboards'); - }); + describe('submit button', () => { + const endpointRequest = [ + operationsSettingsEndpoint, + { + project: { + metrics_setting_attributes: { + external_dashboard_url: externalDashboardUrl, + }, + }, + }, + ]; + + it('renders button label', () => { + mountComponent(); + const submit = wrapper.find(GlButton); + expect(submit.text()).toBe('Save Changes'); }); - describe('submit button', () => { - let submit; + it('submits form on click', () => { + mountComponent(false); + axios.patch.mockResolvedValue(); + wrapper.find(GlButton).trigger('click'); + + expect(axios.patch).toHaveBeenCalledWith(...endpointRequest); - beforeEach(() => { - submit = form.find(GlButton); - }); + return wrapper.vm.$nextTick().then(() => expect(refreshCurrentPage).toHaveBeenCalled()); + }); - it('renders button label', () => { - expect(submit.text()).toBe('Save Changes'); - }); + it('creates flash banner on error', () => { + mountComponent(false); + const message = 'mockErrorMessage'; + axios.patch.mockRejectedValue({ response: { data: { message } } }); + wrapper.find(GlButton).trigger('click'); + + expect(axios.patch).toHaveBeenCalledWith(...endpointRequest); + + return wrapper.vm + .$nextTick() + .then(jest.runAllTicks) + .then(() => + expect(createFlash).toHaveBeenCalledWith( + `There was an error saving your changes. ${message}`, + 'alert', + ), + ); }); }); }); diff --git a/spec/frontend/operation_settings/store/mutations_spec.js b/spec/frontend/operation_settings/store/mutations_spec.js new file mode 100644 index 00000000000..1854142c89a --- /dev/null +++ b/spec/frontend/operation_settings/store/mutations_spec.js @@ -0,0 +1,19 @@ +import mutations from '~/operation_settings/store/mutations'; +import createState from '~/operation_settings/store/state'; + +describe('operation settings mutations', () => { + let localState; + + beforeEach(() => { + localState = createState(); + }); + + describe('SET_EXTERNAL_DASHBOARD_URL', () => { + it('sets externalDashboardUrl', () => { + const mockUrl = 'mockUrl'; + mutations.SET_EXTERNAL_DASHBOARD_URL(localState, mockUrl); + + expect(localState.externalDashboardUrl).toBe(mockUrl); + }); + }); +}); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 6e16ab64be2..cea8cb18918 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -393,7 +393,7 @@ describe('Dashboard', () => { hasMetrics: true, showPanels: false, showTimeWindowDropdown: false, - externalDashboardPath: '/mockPath', + externalDashboardUrl: '/mockUrl', }, store, }); @@ -419,7 +419,7 @@ describe('Dashboard', () => { hasMetrics: true, showPanels: false, showTimeWindowDropdown: false, - externalDashboardPath: '', + externalDashboardUrl: '', }, store, }); -- cgit v1.2.1