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
This commit is contained in:
commit
58b52e4517
12 changed files with 228 additions and 76 deletions
|
@ -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" />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<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: '',
|
||||
},
|
||||
externalDashboardHelpPagePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
computed: {
|
||||
...mapState([
|
||||
'externalDashboardHelpPagePath',
|
||||
'externalDashboardUrl',
|
||||
'operationsSettingsEndpoint',
|
||||
]),
|
||||
userDashboardUrl: {
|
||||
get() {
|
||||
return this.externalDashboardUrl;
|
||||
},
|
||||
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>
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
38
app/assets/javascripts/operation_settings/store/actions.js
Normal file
38
app/assets/javascripts/operation_settings/store/actions.js
Normal file
|
@ -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 () => {};
|
16
app/assets/javascripts/operation_settings/store/index.js
Normal file
16
app/assets/javascripts/operation_settings/store/index.js
Normal file
|
@ -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;
|
|
@ -0,0 +1,3 @@
|
|||
/* eslint-disable import/prefer-default-export */
|
||||
|
||||
export const SET_EXTERNAL_DASHBOARD_URL = 'SET_EXTERNAL_DASHBOARD_URL';
|
|
@ -0,0 +1,7 @@
|
|||
import * as types from './mutation_types';
|
||||
|
||||
export default {
|
||||
[types.SET_EXTERNAL_DASHBOARD_URL](state, url) {
|
||||
state.externalDashboardUrl = url;
|
||||
},
|
||||
};
|
5
app/assets/javascripts/operation_settings/store/state.js
Normal file
5
app/assets/javascripts/operation_settings/store/state.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default (initialState = {}) => ({
|
||||
externalDashboardUrl: initialState.externalDashboardUrl || '',
|
||||
operationsSettingsEndpoint: initialState.operationsSettingsEndpoint,
|
||||
externalDashboardHelpPagePath: initialState.externalDashboardHelpPagePath,
|
||||
});
|
|
@ -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') } } }
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
it('uses label text', () => {
|
||||
expect(formGroup.attributes().label).toBe('Full dashboard URL');
|
||||
});
|
||||
|
||||
it('uses description text', () => {
|
||||
expect(formGroup.attributes().description).toBe(
|
||||
'Enter the URL of the dashboard you want to link to',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('external dashboard url', () => {
|
||||
describe('input label', () => {
|
||||
let formGroup;
|
||||
describe('input field', () => {
|
||||
let input;
|
||||
|
||||
beforeEach(() => {
|
||||
formGroup = form.find(GlFormGroup);
|
||||
});
|
||||
beforeEach(() => {
|
||||
mountComponent();
|
||||
input = wrapper.find(GlFormInput);
|
||||
});
|
||||
|
||||
it('uses label text', () => {
|
||||
expect(formGroup.attributes().label).toBe('Full dashboard URL');
|
||||
});
|
||||
it('defaults to externalDashboardUrl', () => {
|
||||
expect(input.attributes().value).toBe(externalDashboardUrl);
|
||||
});
|
||||
|
||||
it('uses description text', () => {
|
||||
expect(formGroup.attributes().description).toBe(
|
||||
'Enter the URL of the dashboard you want to link to',
|
||||
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');
|
||||
});
|
||||
|
||||
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',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('input field', () => {
|
||||
let input;
|
||||
|
||||
beforeEach(() => {
|
||||
input = form.find(GlFormInput);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('submit button', () => {
|
||||
let submit;
|
||||
|
||||
beforeEach(() => {
|
||||
submit = form.find(GlButton);
|
||||
});
|
||||
|
||||
it('renders button label', () => {
|
||||
expect(submit.text()).toBe('Save Changes');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
19
spec/frontend/operation_settings/store/mutations_spec.js
Normal file
19
spec/frontend/operation_settings/store/mutations_spec.js
Normal file
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue