Commit 57f6be00 authored by Adriel Santiago's avatar Adriel Santiago Committed by Fatih Acet

Handle external dashboard form submission

Connect frontend UI with backend api for external dashboard link
parent 52b2b325
......@@ -38,7 +38,7 @@ export default {
GlModalDirective,
},
props: {
externalDashboardPath: {
externalDashboardUrl: {
type: String,
required: false,
default: '',
......@@ -299,10 +299,11 @@ export default {
</gl-modal>
</div>
<gl-button
v-if="externalDashboardPath.length"
v-if="externalDashboardUrl.length"
class="js-external-dashboard-link prepend-left-8"
variant="primary"
:href="externalDashboardPath"
:href="externalDashboardUrl"
target="_blank"
>
{{ __('View full dashboard') }}
<icon name="external-link" />
......
<script>
import { mapState, mapActions } from 'vuex';
import { GlButton, GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui';
export default {
......@@ -8,17 +9,24 @@ export default {
GlFormInput,
GlLink,
},
props: {
externalDashboardPath: {
type: String,
required: false,
default: '',
computed: {
...mapState([
'externalDashboardHelpPagePath',
'externalDashboardUrl',
'operationsSettingsEndpoint',
]),
userDashboardUrl: {
get() {
return this.externalDashboardUrl;
},
externalDashboardHelpPagePath: {
type: String,
required: true,
set(url) {
this.setExternalDashboardUrl(url);
},
},
},
methods: {
...mapActions(['setExternalDashboardUrl', 'updateExternalDashboardUrl']),
},
};
</script>
......@@ -45,11 +53,12 @@ export default {
:description="s__('ExternalMetrics|Enter the URL of the dashboard you want to link to')"
>
<gl-form-input
:value="externalDashboardPath"
v-model="userDashboardUrl"
placeholder="https://my-org.gitlab.io/my-dashboards"
@keydown.enter.native.prevent="updateExternalDashboardUrl"
/>
</gl-form-group>
<gl-button variant="success">
<gl-button variant="success" @click="updateExternalDashboardUrl">
{{ __('Save Changes') }}
</gl-button>
</form>
......
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);
},
});
};
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 () => {};
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;
/* eslint-disable import/prefer-default-export */
export const SET_EXTERNAL_DASHBOARD_URL = 'SET_EXTERNAL_DASHBOARD_URL';
import * as types from './mutation_types';
export default {
[types.SET_EXTERNAL_DASHBOARD_URL](state, url) {
state.externalDashboardUrl = url;
},
};
export default (initialState = {}) => ({
externalDashboardUrl: initialState.externalDashboardUrl || '',
operationsSettingsEndpoint: initialState.operationsSettingsEndpoint,
externalDashboardHelpPagePath: initialState.externalDashboardHelpPagePath,
});
.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') } } }
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,
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,18 +77,12 @@ describe('operation settings external dashboard component', () => {
});
describe('form', () => {
let form;
beforeEach(() => {
form = wrapper.find('form');
});
describe('external dashboard url', () => {
describe('input label', () => {
let formGroup;
beforeEach(() => {
formGroup = form.find(GlFormGroup);
mountComponent();
formGroup = wrapper.find(GlFormGroup);
});
it('uses label text', () => {
......@@ -80,11 +100,12 @@ describe('operation settings external dashboard component', () => {
let input;
beforeEach(() => {
input = form.find(GlFormInput);
mountComponent();
input = wrapper.find(GlFormInput);
});
it('defaults to externalDashboardPath prop', () => {
expect(input.attributes().value).toBe(externalDashboardPath);
it('defaults to externalDashboardUrl', () => {
expect(input.attributes().value).toBe(externalDashboardUrl);
});
it('uses a placeholder', () => {
......@@ -93,15 +114,50 @@ describe('operation settings external dashboard component', () => {
});
describe('submit button', () => {
let submit;
beforeEach(() => {
submit = form.find(GlButton);
});
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');
});
it('submits form on click', () => {
mountComponent(false);
axios.patch.mockResolvedValue();
wrapper.find(GlButton).trigger('click');
expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
return wrapper.vm.$nextTick().then(() => expect(refreshCurrentPage).toHaveBeenCalled());
});
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',
),
);
});
});
});
......
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);
});
});
});
......@@ -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,
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment