Commit 58b52e45 authored by Fatih Acet's avatar Fatih Acet

Merge branch 'fe-61453-helpers' into 'master'

Connect Frontend and backend for external dashboard link

Closes #61453

See merge request gitlab-org/gitlab-ce!28431
parents 52b2b325 57f6be00
......@@ -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