Commit 5ad55f92 authored by Miguel Rincon's avatar Miguel Rincon

Add first iteration of panel builder component

This change adds a new panel builder component that will show
a panel in the future, this panel builder is connected to the URL
query parameters and calls a stubbed endpoint that does not work yet.
parent dab994e3
<script>
import { mapActions, mapState } from 'vuex';
import { GlCard, GlForm, GlFormGroup, GlFormTextarea, GlButton, GlAlert } from '@gitlab/ui';
import DashboardPanel from './dashboard_panel.vue';
const initialYml = `title:
y_label:
type: area-chart
metrics:
- query_range:
label:
`;
export default {
components: {
GlCard,
GlForm,
GlFormGroup,
GlFormTextarea,
GlButton,
GlAlert,
DashboardPanel,
},
data() {
return {
yml: initialYml,
};
},
computed: {
...mapState('monitoringDashboard', [
'panelPreviewIsLoading',
'panelPreviewError',
'panelPreviewGraphData',
]),
},
methods: {
...mapActions('monitoringDashboard', ['fetchPanelPreview']),
onSubmit() {
this.fetchPanelPreview(this.yml);
},
},
};
</script>
<template>
<div>
<gl-card>
<template #header>
<h2 class="gl-font-size-h2 gl-my-3">{{ s__('Metrics|Define and preview panel') }}</h2>
</template>
<template #default>
<gl-form @submit.prevent="onSubmit">
<gl-form-group
:label="s__('Metrics|Panel YAML')"
:description="s__('Metrics|Define panel YAML to preview panel.')"
label-for="panel-yml-input"
>
<gl-form-textarea
id="panel-yml-input"
v-model="yml"
class="gl-h-200! gl-font-monospace! gl-font-size-monospace!"
/>
</gl-form-group>
<div class="gl-text-right">
<gl-button
ref="clipboardCopyBtn"
variant="success"
category="secondary"
:data-clipboard-text="yml"
@click="$toast.show(s__('Metrics|Panel YAML copied'))"
>
{{ s__('Metrics|Copy YAML') }}
</gl-button>
<gl-button
type="submit"
variant="success"
:disabled="panelPreviewIsLoading"
class="js-no-auto-disable"
>
{{ s__('Metrics|Preview panel') }}
</gl-button>
</div>
</gl-form>
</template>
</gl-card>
<gl-alert v-if="panelPreviewError" variant="warning" :dismissible="false">
{{ panelPreviewError }}
</gl-alert>
<dashboard-panel :graph-data="panelPreviewGraphData" />
</div>
</template>
<script> <script>
import { mapState } from 'vuex';
import { GlButton, GlTooltipDirective } from '@gitlab/ui'; import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import routes from '../router/constants'; import { DASHBOARD_PAGE } from '../router/constants';
import DashboardPanelBuilder from '../components/dashboard_panel_builder.vue';
export default { export default {
components: { components: {
GlButton, GlButton,
DashboardPanelBuilder,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
computed: {
...mapState('monitoringDashboard', ['panelPreviewYml']),
dashboardPageLocation() {
return {
...this.$route,
name: DASHBOARD_PAGE,
};
},
},
i18n: { i18n: {
backToDashboard: s__('Metrics|Back to dashboard'), backToDashboard: s__('Metrics|Back to dashboard'),
}, },
routes,
}; };
</script> </script>
<template> <template>
<div class="gl-display-flex gl-align-items-baseline"> <div class="gl-mt-5">
<gl-button <div class="gl-display-flex gl-align-items-baseline gl-mb-5">
v-gl-tooltip <gl-button
icon="go-back" v-gl-tooltip
:to="{ name: $options.routes.DASHBOARD_PAGE, params: { dashboard: $route.params.dashboard } }" icon="go-back"
:aria-label="$options.i18n.backToDashboard" :to="dashboardPageLocation"
:title="$options.i18n.backToDashboard" :aria-label="$options.i18n.backToDashboard"
class="gl-mr-5" :title="$options.i18n.backToDashboard"
/> class="gl-mr-5"
<h1 class="gl-mt-5 gl-font-size-h1">{{ s__('Metrics|Add panel') }}</h1> />
<h1 class="gl-font-size-h1 gl-my-0">{{ s__('Metrics|Add panel') }}</h1>
<!-- TODO: Add components. See https://gitlab.com/groups/gitlab-org/-/epics/2882 --> </div>
<dashboard-panel-builder />
</div> </div>
</template> </template>
...@@ -41,3 +41,12 @@ export const getPrometheusQueryData = (prometheusEndpoint, params) => ...@@ -41,3 +41,12 @@ export const getPrometheusQueryData = (prometheusEndpoint, params) =>
} }
throw error; throw error;
}); });
// eslint-disable-next-line no-unused-vars
export function getPanelJson(panelPreviewEndpoint, panelPreviewYml) {
// TODO Use a real backend when it's available
// https://gitlab.com/gitlab-org/gitlab/-/issues/228758
// eslint-disable-next-line @gitlab/require-i18n-strings
return Promise.reject(new Error('API Not implemented.'));
}
...@@ -15,7 +15,7 @@ import getAnnotations from '../queries/getAnnotations.query.graphql'; ...@@ -15,7 +15,7 @@ import getAnnotations from '../queries/getAnnotations.query.graphql';
import getDashboardValidationWarnings from '../queries/getDashboardValidationWarnings.query.graphql'; import getDashboardValidationWarnings from '../queries/getDashboardValidationWarnings.query.graphql';
import { convertObjectPropsToCamelCase } from '../../lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '../../lib/utils/common_utils';
import { s__, sprintf } from '../../locale'; import { s__, sprintf } from '../../locale';
import { getDashboard, getPrometheusQueryData } from '../requests'; import { getDashboard, getPrometheusQueryData, getPanelJson } from '../requests';
import { ENVIRONMENT_AVAILABLE_STATE, DEFAULT_DASHBOARD_PATH, VARIABLE_TYPES } from '../constants'; import { ENVIRONMENT_AVAILABLE_STATE, DEFAULT_DASHBOARD_PATH, VARIABLE_TYPES } from '../constants';
...@@ -473,3 +473,30 @@ export const fetchVariableMetricLabelValues = ({ state, commit }, { defaultQuery ...@@ -473,3 +473,30 @@ export const fetchVariableMetricLabelValues = ({ state, commit }, { defaultQuery
return Promise.all(optionsRequests); return Promise.all(optionsRequests);
}; };
// Panel Builder
export const fetchPanelPreview = ({ state, commit, dispatch }, panelPreviewYml) => {
if (!panelPreviewYml) {
return null;
}
commit(types.REQUEST_PANEL_PREVIEW, panelPreviewYml);
return getPanelJson(state.panelPreviewEndpoint, panelPreviewYml)
.then(data => {
commit(types.RECEIVE_PANEL_PREVIEW_SUCCESS, data);
dispatch('fetchPanelPreviewMetrics');
})
.catch(error => {
commit(types.RECEIVE_PANEL_PREVIEW_FAILURE, error);
});
};
export const fetchPanelPreviewMetrics = () => {
// TODO Use a axios mock instead of spy when backend is implemented
// https://gitlab.com/gitlab-org/gitlab/-/issues/228758
// eslint-disable-next-line @gitlab/require-i18n-strings
throw new Error('Not implemented');
};
...@@ -46,3 +46,8 @@ export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER'; ...@@ -46,3 +46,8 @@ export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
export const SET_PANEL_GROUP_METRICS = 'SET_PANEL_GROUP_METRICS'; export const SET_PANEL_GROUP_METRICS = 'SET_PANEL_GROUP_METRICS';
export const SET_ENVIRONMENTS_FILTER = 'SET_ENVIRONMENTS_FILTER'; export const SET_ENVIRONMENTS_FILTER = 'SET_ENVIRONMENTS_FILTER';
export const SET_EXPANDED_PANEL = 'SET_EXPANDED_PANEL'; export const SET_EXPANDED_PANEL = 'SET_EXPANDED_PANEL';
// Panel preview
export const REQUEST_PANEL_PREVIEW = 'REQUEST_PANEL_PREVIEW';
export const RECEIVE_PANEL_PREVIEW_SUCCESS = 'RECEIVE_PANEL_PREVIEW_SUCCESS';
export const RECEIVE_PANEL_PREVIEW_FAILURE = 'RECEIVE_PANEL_PREVIEW_FAILURE';
import Vue from 'vue'; import Vue from 'vue';
import { pick } from 'lodash'; import { pick } from 'lodash';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { mapToDashboardViewModel, normalizeQueryResponseData } from './utils'; import { mapToDashboardViewModel, mapPanelToViewModel, normalizeQueryResponseData } from './utils';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils'; import { BACKOFF_TIMEOUT } from '~/lib/utils/common_utils';
import { dashboardEmptyStates, endpointKeys, initialStateKeys, metricStates } from '../constants'; import { dashboardEmptyStates, endpointKeys, initialStateKeys, metricStates } from '../constants';
import { optionsFromSeriesData } from './variable_mapping'; import { optionsFromSeriesData } from './variable_mapping';
...@@ -218,4 +218,24 @@ export default { ...@@ -218,4 +218,24 @@ export default {
// Add new options with assign to ensure Vue reactivity // Add new options with assign to ensure Vue reactivity
Object.assign(variable.options, { values }); Object.assign(variable.options, { values });
}, },
[types.REQUEST_PANEL_PREVIEW](state, panelPreviewYml) {
state.panelPreviewIsLoading = true;
state.panelPreviewYml = panelPreviewYml;
state.panelPreviewGraphData = null;
state.panelPreviewError = null;
},
[types.RECEIVE_PANEL_PREVIEW_SUCCESS](state, payload) {
state.panelPreviewIsLoading = false;
state.panelPreviewGraphData = mapPanelToViewModel(payload);
state.panelPreviewError = null;
},
[types.RECEIVE_PANEL_PREVIEW_FAILURE](state, error) {
state.panelPreviewIsLoading = false;
state.panelPreviewGraphData = null;
state.panelPreviewError = error;
},
}; };
...@@ -59,6 +59,13 @@ export default () => ({ ...@@ -59,6 +59,13 @@ export default () => ({
* via the dashboard yml file. * via the dashboard yml file.
*/ */
links: [], links: [],
// Panel editor / builder
panelPreviewYml: '',
panelPreviewIsLoading: false,
panelPreviewGraphData: null,
panelPreviewError: null,
// Other project data // Other project data
dashboardTimezone: timezones.LOCAL, dashboardTimezone: timezones.LOCAL,
annotations: [], annotations: [],
......
...@@ -82,6 +82,10 @@ ...@@ -82,6 +82,10 @@
.gl-h-32 { height: px-to-rem($grid-size * 4); } .gl-h-32 { height: px-to-rem($grid-size * 4); }
.gl-h-64 { height: px-to-rem($grid-size * 8); } .gl-h-64 { height: px-to-rem($grid-size * 8); }
// Migrate this to Gitlab UI when FF is removed
// https://gitlab.com/groups/gitlab-org/-/epics/2882
.gl-h-200\! { height: px-to-rem($grid-size * 25) !important; }
.d-sm-table-column { .d-sm-table-column {
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
display: table-column !important; display: table-column !important;
......
...@@ -14999,6 +14999,9 @@ msgstr "" ...@@ -14999,6 +14999,9 @@ msgstr ""
msgid "Metrics|Check out the CI/CD documentation on deploying to an environment" msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
msgstr "" msgstr ""
msgid "Metrics|Copy YAML"
msgstr ""
msgid "Metrics|Create custom dashboard %{fileName}" msgid "Metrics|Create custom dashboard %{fileName}"
msgstr "" msgstr ""
...@@ -15017,6 +15020,12 @@ msgstr "" ...@@ -15017,6 +15020,12 @@ msgstr ""
msgid "Metrics|Current" msgid "Metrics|Current"
msgstr "" msgstr ""
msgid "Metrics|Define and preview panel"
msgstr ""
msgid "Metrics|Define panel YAML to preview panel."
msgstr ""
msgid "Metrics|Delete metric" msgid "Metrics|Delete metric"
msgstr "" msgstr ""
...@@ -15085,6 +15094,15 @@ msgstr "" ...@@ -15085,6 +15094,15 @@ msgstr ""
msgid "Metrics|Open repository" msgid "Metrics|Open repository"
msgstr "" msgstr ""
msgid "Metrics|Panel YAML"
msgstr ""
msgid "Metrics|Panel YAML copied"
msgstr ""
msgid "Metrics|Preview panel"
msgstr ""
msgid "Metrics|PromQL query is valid" msgid "Metrics|PromQL query is valid"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import { GlCard, GlForm, GlFormTextarea, GlAlert } from '@gitlab/ui';
import { createStore } from '~/monitoring/stores';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import * as types from '~/monitoring/stores/mutation_types';
import { metricsDashboardResponse } from '../fixture_data';
import DashboardPanelBuilder from '~/monitoring/components/dashboard_panel_builder.vue';
const mockPanel = metricsDashboardResponse.dashboard.panel_groups[0].panels[0];
describe('dashboard invalid url parameters', () => {
let store;
let wrapper;
let mockShowToast;
const createComponent = (props = {}, options = {}) => {
wrapper = shallowMount(DashboardPanelBuilder, {
propsData: { ...props },
store,
stubs: {
GlCard,
},
mocks: {
$toast: {
show: mockShowToast,
},
},
options,
});
};
const findForm = () => wrapper.find(GlForm);
const findTxtArea = () => findForm().find(GlFormTextarea);
const findSubmitBtn = () => findForm().find('[type="submit"]');
const findClipboardCopyBtn = () => wrapper.find({ ref: 'clipboardCopyBtn' });
const findPanel = () => wrapper.find(DashboardPanel);
beforeEach(() => {
mockShowToast = jest.fn();
store = createStore();
createComponent();
jest.spyOn(store, 'dispatch').mockResolvedValue();
});
afterEach(() => {});
it('is mounted', () => {
expect(wrapper.exists()).toBe(true);
});
it('displays an empty dashboard panel', () => {
expect(findPanel().exists()).toBe(true);
expect(findPanel().props('graphData')).toBe(null);
});
it('does not fetch initial data by default', () => {
expect(store.dispatch).not.toHaveBeenCalled();
});
describe('yml form', () => {
it('form exists and can be submitted', () => {
expect(findForm().exists()).toBe(true);
expect(findSubmitBtn().exists()).toBe(true);
expect(findSubmitBtn().is('[disabled]')).toBe(false);
});
it('form has a text area with a default value', () => {
expect(findTxtArea().exists()).toBe(true);
const value = findTxtArea().attributes('value');
// Panel definition should contain a title and a type
expect(value).toContain('title:');
expect(value).toContain('type:');
});
it('"copy to clipboard" button works', () => {
findClipboardCopyBtn().vm.$emit('click');
const clipboardText = findClipboardCopyBtn().attributes('data-clipboard-text');
expect(clipboardText).toContain('title:');
expect(clipboardText).toContain('type:');
expect(mockShowToast).toHaveBeenCalledTimes(1);
});
it('on submit fetches a panel preview', () => {
findForm().vm.$emit('submit', new Event('submit'));
return wrapper.vm.$nextTick().then(() => {
expect(store.dispatch).toHaveBeenCalledWith(
'monitoringDashboard/fetchPanelPreview',
expect.stringContaining('title:'),
);
});
});
describe('when form is submitted', () => {
beforeEach(() => {
store.commit(`monitoringDashboard/${types.REQUEST_PANEL_PREVIEW}`, 'mock yml content');
return wrapper.vm.$nextTick();
});
it('submit button is disabled', () => {
expect(findSubmitBtn().is('[disabled]')).toBe(true);
});
});
});
describe('when there is an error', () => {
const mockError = 'an error ocurred!';
beforeEach(() => {
store.commit(`monitoringDashboard/${types.RECEIVE_PANEL_PREVIEW_FAILURE}`, mockError);
return wrapper.vm.$nextTick();
});
it('displays an alert', () => {
expect(wrapper.find(GlAlert).exists()).toBe(true);
expect(wrapper.find(GlAlert).text()).toBe(mockError);
});
it('displays an empty dashboard panel', () => {
expect(findPanel().props('graphData')).toBe(null);
});
});
describe('when panel data is available', () => {
beforeEach(() => {
store.commit(`monitoringDashboard/${types.RECEIVE_PANEL_PREVIEW_SUCCESS}`, mockPanel);
return wrapper.vm.$nextTick();
});
it('displays no alert', () => {
expect(wrapper.find(GlAlert).exists()).toBe(false);
});
it('displays panel with data', () => {
const { title, type } = wrapper.find(DashboardPanel).props('graphData');
expect(title).toBe(mockPanel.title);
expect(type).toBe(mockPanel.type);
});
});
});
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { DASHBOARD_PAGE } from '~/monitoring/router/constants'; import { DASHBOARD_PAGE, PANEL_NEW_PAGE } from '~/monitoring/router/constants';
import { createStore } from '~/monitoring/stores';
import DashboardPanelBuilder from '~/monitoring/components/dashboard_panel_builder.vue';
import PanelNewPage from '~/monitoring/pages/panel_new_page.vue'; import PanelNewPage from '~/monitoring/pages/panel_new_page.vue';
const dashboard = 'dashboard.yml'; const dashboard = 'dashboard.yml';
...@@ -15,26 +18,37 @@ const GlButtonStub = { ...@@ -15,26 +18,37 @@ const GlButtonStub = {
}; };
describe('monitoring/pages/panel_new_page', () => { describe('monitoring/pages/panel_new_page', () => {
let store;
let wrapper; let wrapper;
let $route; let $route;
let $router;
const mountComponent = (propsData = {}, routeParams = { dashboard }) => { const mountComponent = (propsData = {}, route) => {
$route = { $route = route ?? { name: PANEL_NEW_PAGE, params: { dashboard } };
params: routeParams, $router = {
push: jest.fn(),
}; };
wrapper = shallowMount(PanelNewPage, { wrapper = shallowMount(PanelNewPage, {
propsData, propsData,
store,
stubs: { stubs: {
GlButton: GlButtonStub, GlButton: GlButtonStub,
}, },
mocks: { mocks: {
$router,
$route, $route,
}, },
}); });
}; };
const findBackButton = () => wrapper.find(GlButtonStub); const findBackButton = () => wrapper.find(GlButtonStub);
const findPanelBuilder = () => wrapper.find(DashboardPanelBuilder);
beforeEach(() => {
store = createStore();
mountComponent();
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -42,18 +56,43 @@ describe('monitoring/pages/panel_new_page', () => { ...@@ -42,18 +56,43 @@ describe('monitoring/pages/panel_new_page', () => {
describe('back to dashboard button', () => { describe('back to dashboard button', () => {
it('is rendered', () => { it('is rendered', () => {
mountComponent();
expect(findBackButton().exists()).toBe(true); expect(findBackButton().exists()).toBe(true);
expect(findBackButton().props('icon')).toBe('go-back'); expect(findBackButton().props('icon')).toBe('go-back');
}); });
it('links back to the dashboard', () => { it('links back to the dashboard', () => {
const dashboardLocation = { expect(findBackButton().props('to')).toEqual({
name: DASHBOARD_PAGE, name: DASHBOARD_PAGE,
params: { dashboard }, params: { dashboard },
});
});
it('links back to the dashboard while preserving query params', () => {
$route = {
name: PANEL_NEW_PAGE,
params: { dashboard },
query: { another: 'param' },
}; };
expect(findBackButton().props('to')).toEqual(dashboardLocation); mountComponent({}, $route);
expect(findBackButton().props('to')).toEqual({
name: DASHBOARD_PAGE,
params: { dashboard },
query: { another: 'param' },
});
});
});
describe('dashboard panel builder', () => {
it('is rendered', () => {
expect(findPanelBuilder().exists()).toBe(true);
});
});
describe('page routing', () => {
it('route is not updated by default', () => {
expect($router.push).not.toHaveBeenCalled();
}); });
}); });
}); });
...@@ -9,6 +9,7 @@ import { defaultTimeRange } from '~/vue_shared/constants'; ...@@ -9,6 +9,7 @@ import { defaultTimeRange } from '~/vue_shared/constants';
import * as getters from '~/monitoring/stores/getters'; import * as getters from '~/monitoring/stores/getters';
import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants'; import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants';
import { backoffMockImplementation } from 'jest/helpers/backoff_helper'; import { backoffMockImplementation } from 'jest/helpers/backoff_helper';
import * as requests from '~/monitoring/requests';
import { createStore } from '~/monitoring/stores'; import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types'; import * as types from '~/monitoring/stores/mutation_types';
...@@ -31,6 +32,7 @@ import { ...@@ -31,6 +32,7 @@ import {
duplicateSystemDashboard, duplicateSystemDashboard,
updateVariablesAndFetchData, updateVariablesAndFetchData,
fetchVariableMetricLabelValues, fetchVariableMetricLabelValues,
fetchPanelPreview,
} from '~/monitoring/stores/actions'; } from '~/monitoring/stores/actions';
import { import {
gqClient, gqClient,
...@@ -1154,4 +1156,56 @@ describe('Monitoring store actions', () => { ...@@ -1154,4 +1156,56 @@ describe('Monitoring store actions', () => {
); );
}); });
}); });
describe('fetchPanelPreview', () => {
const mockYmlContent = 'mock yml content';
it('should not commit or dispatch if payload is empty', () => {
testAction(fetchPanelPreview, '', state, [], []);
});
it('should store the yml content and panel in the store and fetch corresponding metrics', () => {
const mockPanel = {
title: 'title',
type: 'area-chart',
};
// TODO Use a axios mock instead of spy when backend is implemented
// https://gitlab.com/gitlab-org/gitlab/-/issues/228758
jest.spyOn(requests, 'getPanelJson').mockResolvedValue(mockPanel);
testAction(
fetchPanelPreview,
'mock yml content',
state,
[
{ type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
{ type: types.RECEIVE_PANEL_PREVIEW_SUCCESS, payload: mockPanel },
],
[
{
type: 'fetchPanelPreviewMetrics',
},
],
);
});
it('should commit a failure when backend fails', () => {
const mockError = 'error';
// TODO Use a axios mock instead of spy when backend is implemented
// https://gitlab.com/gitlab-org/gitlab/-/issues/228758
jest.spyOn(requests, 'getPanelJson').mockRejectedValue(mockError);
testAction(
fetchPanelPreview,
mockYmlContent,
state,
[
{ type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
{ type: types.RECEIVE_PANEL_PREVIEW_FAILURE, payload: mockError },
],
[],
);
});
});
}); });
...@@ -488,4 +488,42 @@ describe('Monitoring mutations', () => { ...@@ -488,4 +488,42 @@ describe('Monitoring mutations', () => {
}); });
}); });
}); });
describe('REQUEST_PANEL_PREVIEW', () => {
it('saves yml content and resets other preview data', () => {
const mockYmlContent = 'mock yml content';
mutations[types.REQUEST_PANEL_PREVIEW](stateCopy, mockYmlContent);
expect(stateCopy.panelPreviewIsLoading).toBe(true);
expect(stateCopy.panelPreviewYml).toBe(mockYmlContent);
expect(stateCopy.panelPreviewGraphData).toBe(null);
expect(stateCopy.panelPreviewError).toBe(null);
});
});
describe('RECEIVE_PANEL_PREVIEW_SUCCESS', () => {
it('saves graph data', () => {
mutations[types.RECEIVE_PANEL_PREVIEW_SUCCESS](stateCopy, {
title: 'My Title',
type: 'area-chart',
});
expect(stateCopy.panelPreviewIsLoading).toBe(false);
expect(stateCopy.panelPreviewGraphData).toMatchObject({
title: 'My Title',
type: 'area-chart',
});
expect(stateCopy.panelPreviewError).toBe(null);
});
});
describe('RECEIVE_PANEL_PREVIEW_FAILURE', () => {
it('saves graph data', () => {
mutations[types.RECEIVE_PANEL_PREVIEW_FAILURE](stateCopy, 'Error!');
expect(stateCopy.panelPreviewIsLoading).toBe(false);
expect(stateCopy.panelPreviewGraphData).toBe(null);
expect(stateCopy.panelPreviewError).toBe('Error!');
});
});
}); });
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