Commit 7ee61f74 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'tr-remove-unfurled-chart' into 'master'

Support expanding and collapsing embedded charts

See merge request gitlab-org/gitlab!23929
parents 871f3e30 89194380
import Vue from 'vue';
import Metrics from '~/monitoring/components/embed.vue';
import { createStore } from '~/monitoring/stores';
import EmbedGroup from '~/monitoring/components/embeds/embed_group.vue';
import { createStore } from '~/monitoring/stores/embed_group/';
// TODO: Handle copy-pasting - https://gitlab.com/gitlab-org/gitlab-foss/issues/64369.
export default function renderMetrics(elements) {
......@@ -8,16 +8,36 @@ export default function renderMetrics(elements) {
return;
}
const EmbedGroupComponent = Vue.extend(EmbedGroup);
const wrapperList = [];
elements.forEach(element => {
const { dashboardUrl } = element.dataset;
const MetricsComponent = Vue.extend(Metrics);
let wrapper;
const { previousElementSibling } = element;
const isFirstElementInGroup = !previousElementSibling?.urls;
if (isFirstElementInGroup) {
wrapper = document.createElement('div');
wrapper.urls = [element.dataset.dashboardUrl];
element.parentNode.insertBefore(wrapper, element);
wrapperList.push(wrapper);
} else {
wrapper = previousElementSibling;
wrapper.urls.push(element.dataset.dashboardUrl);
}
// Clean up processed element
element.parentNode.removeChild(element);
});
wrapperList.forEach(wrapper => {
// eslint-disable-next-line no-new
new MetricsComponent({
el: element,
new EmbedGroupComponent({
el: wrapper,
store: createStore(),
propsData: {
dashboardUrl,
urls: wrapper.urls,
},
});
});
......
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import sum from 'lodash/sum';
import { GlButton, GlCard, GlIcon } from '@gitlab/ui';
import { n__ } from '~/locale';
import { monitoringDashboard } from '~/monitoring/stores';
import MetricEmbed from './metric_embed.vue';
export default {
components: {
GlButton,
GlCard,
GlIcon,
MetricEmbed,
},
props: {
urls: {
type: Array,
required: true,
validator: urls => urls.length > 0,
},
},
data() {
return {
isCollapsed: false,
};
},
computed: {
...mapState('embedGroup', ['module']),
...mapGetters('embedGroup', ['metricsWithData']),
arrowIconName() {
return this.isCollapsed ? 'chevron-right' : 'chevron-down';
},
bodyClass() {
return ['border-top', 'pl-3', 'pt-3', { 'd-none': this.isCollapsed }];
},
buttonLabel() {
return this.isCollapsed
? n__('View chart', 'View charts', this.numCharts)
: n__('Hide chart', 'Hide charts', this.numCharts);
},
containerClass() {
return this.isSingleChart ? 'col-lg-12' : 'col-lg-6';
},
numCharts() {
if (this.metricsWithData === null) {
return 0;
}
return sum(this.metricsWithData);
},
isSingleChart() {
return this.numCharts === 1;
},
},
created() {
this.urls.forEach((url, index) => {
const name = this.getNamespace(index);
this.$store.registerModule(name, monitoringDashboard);
this.addModule(name);
});
},
methods: {
...mapActions('embedGroup', ['addModule']),
getNamespace(id) {
return `monitoringDashboard/${id}`;
},
toggleCollapsed() {
this.isCollapsed = !this.isCollapsed;
},
},
};
</script>
<template>
<gl-card
v-show="numCharts > 0"
class="collapsible-card border p-0 mb-3"
header-class="d-flex align-items-center border-bottom-0 py-2"
:body-class="bodyClass"
>
<template #header>
<gl-button
class="collapsible-card-btn d-flex text-decoration-none"
:aria-label="buttonLabel"
variant="link"
@click="toggleCollapsed"
>
<gl-icon class="mr-1" :name="arrowIconName" />
{{ buttonLabel }}
</gl-button>
</template>
<div class="d-flex flex-wrap">
<metric-embed
v-for="(url, index) in urls"
:key="`${index}/${url}`"
:dashboard-url="url"
:namespace="getNamespace(index)"
:container-class="containerClass"
/>
</div>
</gl-card>
</template>
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
import { mapState, mapActions } from 'vuex';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
import { timeRangeFromUrl, removeTimeRangeParams } from '../utils';
import { sidebarAnimationDuration } from '../constants';
import { defaultTimeRange } from '~/vue_shared/constants';
import { timeRangeFromUrl, removeTimeRangeParams } from '../../utils';
import { sidebarAnimationDuration } from '../../constants';
let sidebarMutationObserver;
......@@ -13,10 +13,20 @@ export default {
PanelType,
},
props: {
containerClass: {
type: String,
required: false,
default: 'col-lg-12',
},
dashboardUrl: {
type: String,
required: true,
},
namespace: {
type: String,
required: false,
default: 'monitoringDashboard',
},
},
data() {
const timeRange = timeRangeFromUrl(this.dashboardUrl) || defaultTimeRange;
......@@ -26,21 +36,32 @@ export default {
};
},
computed: {
...mapState('monitoringDashboard', ['dashboard']),
...mapGetters('monitoringDashboard', ['metricsWithData']),
...mapState({
dashboard(state) {
return state[this.namespace].dashboard;
},
metricsWithData(state, getters) {
return getters[`${this.namespace}/metricsWithData`]();
},
}),
charts() {
if (!this.dashboard || !this.dashboard.panelGroups) {
return [];
}
const groupWithMetrics = this.dashboard.panelGroups.find(group =>
group.panels.find(chart => this.chartHasData(chart)),
) || { panels: [] };
return groupWithMetrics.panels.filter(chart => this.chartHasData(chart));
return this.dashboard.panelGroups.reduce(
(acc, currentGroup) => acc.concat(currentGroup.panels.filter(this.chartHasData)),
[],
);
},
isSingleChart() {
return this.charts.length === 1;
},
embedClass() {
return this.isSingleChart ? this.containerClass : 'col-lg-12';
},
panelClass() {
return this.isSingleChart ? 'col-lg-12' : 'col-lg-6';
},
},
mounted() {
this.setInitialState();
......@@ -60,15 +81,27 @@ export default {
}
},
methods: {
...mapActions('monitoringDashboard', [
'setTimeRange',
'fetchDashboard',
'setEndpoints',
'setFeatureFlags',
'setShowErrorBanner',
]),
// Use function args to support dynamic namespaces in mapXXX helpers. Pattern described
// in https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
...mapActions({
setTimeRange(dispatch, payload) {
return dispatch(`${this.namespace}/setTimeRange`, payload);
},
fetchDashboard(dispatch, payload) {
return dispatch(`${this.namespace}/fetchDashboard`, payload);
},
setEndpoints(dispatch, payload) {
return dispatch(`${this.namespace}/setEndpoints`, payload);
},
setFeatureFlags(dispatch, payload) {
return dispatch(`${this.namespace}/setFeatureFlags`, payload);
},
setShowErrorBanner(dispatch, payload) {
return dispatch(`${this.namespace}/setShowErrorBanner`, payload);
},
}),
chartHasData(chart) {
return chart.metrics.some(metric => this.metricsWithData().includes(metric.metricId));
return chart.metrics.some(metric => this.metricsWithData.includes(metric.metricId));
},
onSidebarMutation() {
setTimeout(() => {
......@@ -85,15 +118,14 @@ export default {
};
</script>
<template>
<div class="metrics-embed" :class="{ 'd-inline-flex col-lg-6 p-0': isSingleChart }">
<div v-if="charts.length" class="row w-100 m-n2 pb-4">
<panel-type
v-for="(graphData, graphIndex) in charts"
:key="`panel-type-${graphIndex}`"
class="w-100"
:graph-data="graphData"
:group-id="dashboardUrl"
/>
</div>
<div class="metrics-embed p-0 d-flex flex-wrap" :class="embedClass">
<panel-type
v-for="(graphData, graphIndex) in charts"
:key="`panel-type-${graphIndex}`"
:class="panelClass"
:graph-data="graphData"
:group-id="dashboardUrl"
:namespace="namespace"
/>
</div>
</template>
......@@ -68,6 +68,11 @@ export default {
required: false,
default: 'panel-type-chart',
},
namespace: {
type: String,
required: false,
default: 'monitoringDashboard',
},
},
data() {
return {
......@@ -76,7 +81,22 @@ export default {
};
},
computed: {
...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']),
// Use functions to support dynamic namespaces in mapXXX helpers. Pattern described
// in https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
...mapState({
deploymentData(state) {
return state[this.namespace].deploymentData;
},
projectPath(state) {
return state[this.namespace].projectPath;
},
logsPath(state) {
return state[this.namespace].logsPath;
},
timeRange(state) {
return state[this.namespace].timeRange;
},
}),
title() {
return this.graphData.title || '';
},
......
import * as types from './mutation_types';
export const addModule = ({ commit }, data) => commit(types.ADD_MODULE, data);
export default () => {};
export const metricsWithData = (state, getters, rootState, rootGetters) =>
state.modules.map(module => rootGetters[`${module}/metricsWithData`]().length);
export default () => {};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
// In practice this store will have a number of `monitoringDashboard` modules added dynamically
export const createStore = () =>
new Vuex.Store({
modules: {
embedGroup: {
namespaced: true,
actions,
getters,
mutations,
state,
},
},
});
export default createStore();
export const ADD_MODULE = 'ADD_MODULE';
export default () => {};
import * as types from './mutation_types';
export default {
[types.ADD_MODULE](state, module) {
state.modules.push(module);
},
};
......@@ -7,16 +7,18 @@ import state from './state';
Vue.use(Vuex);
export const monitoringDashboard = {
namespaced: true,
actions,
getters,
mutations,
state,
};
export const createStore = () =>
new Vuex.Store({
modules: {
monitoringDashboard: {
namespaced: true,
actions,
getters,
mutations,
state,
},
monitoringDashboard,
},
});
......
.collapsible-card {
.collapsible-card-btn {
color: $gl-text-color;
&:hover {
color: $blue-600;
}
}
}
---
title: Allow embedded metrics charts to be hidden
merge_request: 23929
author:
type: added
......@@ -777,7 +777,11 @@ The following requirements must be met for the metric to unfurl:
If all of the above are true, then the metric will unfurl as seen below:
![Embedded Metrics](img/embed_metrics.png)
![Embedded Metrics](img/view_embedded_metrics_v12_10.png)
Metric charts may also be hidden:
![Show Hide](img/hide_embedded_metrics_v12_10.png)
### Embedding metrics in issue templates
......
......@@ -10500,6 +10500,11 @@ msgstr ""
msgid "Hide archived projects"
msgstr ""
msgid "Hide chart"
msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
msgid "Hide file browser"
msgstr ""
......@@ -22361,6 +22366,11 @@ msgstr ""
msgid "View blame prior to this change"
msgstr ""
msgid "View chart"
msgid_plural "View charts"
msgstr[0] ""
msgstr[1] ""
msgid "View dependency details for your project"
msgstr ""
......
import Vue from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import renderMetrics from '~/behaviors/markdown/render_metrics';
const originalExtend = Vue.extend;
const mockEmbedGroup = jest.fn();
describe('Render metrics for Gitlab Flavoured Markdown', () => {
const container = {
Metrics() {},
};
let spyExtend;
beforeEach(() => {
Vue.extend = () => container.Metrics;
spyExtend = jest.spyOn(Vue, 'extend');
});
jest.mock('vue', () => ({ extend: () => mockEmbedGroup }));
jest.mock('~/monitoring/components/embeds/embed_group.vue', () => jest.fn());
jest.mock('~/monitoring/stores/embed_group/', () => ({ createStore: jest.fn() }));
afterEach(() => {
Vue.extend = originalExtend;
});
const getElements = () => Array.from(document.getElementsByClassName('js-render-metrics'));
describe('Render metrics for Gitlab Flavoured Markdown', () => {
it('does nothing when no elements are found', () => {
renderMetrics([]);
expect(spyExtend).not.toHaveBeenCalled();
expect(mockEmbedGroup).not.toHaveBeenCalled();
});
it('renders a vue component when elements are found', () => {
const element = document.createElement('div');
element.setAttribute('data-dashboard-url', TEST_HOST);
document.body.innerHTML = `<div class="js-render-metrics" data-dashboard-url="${TEST_HOST}"></div>`;
renderMetrics([element]);
renderMetrics(getElements());
expect(mockEmbedGroup).toHaveBeenCalledTimes(1);
expect(mockEmbedGroup).toHaveBeenCalledWith(
expect.objectContaining({ propsData: { urls: [`${TEST_HOST}`] } }),
);
});
expect(spyExtend).toHaveBeenCalled();
it('takes sibling metrics and groups them under a shared parent', () => {
document.body.innerHTML = `
<p><span>Hello</span></p>
<div class="js-render-metrics" data-dashboard-url="${TEST_HOST}/1"></div>
<div class="js-render-metrics" data-dashboard-url="${TEST_HOST}/2"></div>
<p><span>Hello</span></p>
<div class="js-render-metrics" data-dashboard-url="${TEST_HOST}/3"></div>
`;
renderMetrics(getElements());
expect(mockEmbedGroup).toHaveBeenCalledTimes(2);
expect(mockEmbedGroup).toHaveBeenCalledWith(
expect.objectContaining({ propsData: { urls: [`${TEST_HOST}/1`, `${TEST_HOST}/2`] } }),
);
expect(mockEmbedGroup).toHaveBeenCalledWith(
expect.objectContaining({ propsData: { urls: [`${TEST_HOST}/3`] } }),
);
});
});
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { GlButton, GlCard } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants';
import EmbedGroup from '~/monitoring/components/embeds/embed_group.vue';
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
import {
addModuleAction,
initialEmbedGroupState,
singleEmbedProps,
dashboardEmbedProps,
multipleEmbedProps,
} from './mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Embed Group', () => {
let wrapper;
let store;
const metricsWithDataGetter = jest.fn();
function mountComponent({ urls = [TEST_HOST], shallow = true, stubs } = {}) {
const mountMethod = shallow ? shallowMount : mount;
wrapper = mountMethod(EmbedGroup, {
localVue,
store,
propsData: {
urls,
},
stubs,
});
}
beforeEach(() => {
store = new Vuex.Store({
modules: {
embedGroup: {
namespaced: true,
actions: { addModule: jest.fn() },
getters: { metricsWithData: metricsWithDataGetter },
state: initialEmbedGroupState,
},
},
});
store.registerModule = jest.fn();
jest.spyOn(store, 'dispatch');
});
afterEach(() => {
metricsWithDataGetter.mockReset();
if (wrapper) {
wrapper.destroy();
}
});
describe('interactivity', () => {
it('hides the component when no chart data is loaded', () => {
metricsWithDataGetter.mockReturnValue([]);
mountComponent();
expect(wrapper.find(GlCard).isVisible()).toBe(false);
});
it('shows the component when chart data is loaded', () => {
metricsWithDataGetter.mockReturnValue([1]);
mountComponent();
expect(wrapper.find(GlCard).isVisible()).toBe(true);
});
it('is expanded by default', () => {
metricsWithDataGetter.mockReturnValue([1]);
mountComponent({ shallow: false, stubs: { MetricEmbed: '<div />' } });
expect(wrapper.find('.card-body').classes()).not.toContain('d-none');
});
it('collapses when clicked', done => {
metricsWithDataGetter.mockReturnValue([1]);
mountComponent({ shallow: false, stubs: { MetricEmbed: '<div />' } });
wrapper.find(GlButton).trigger('click');
wrapper.vm.$nextTick(() => {
expect(wrapper.find('.card-body').classes()).toContain('d-none');
done();
});
});
});
describe('single metrics', () => {
beforeEach(() => {
metricsWithDataGetter.mockReturnValue([1]);
mountComponent();
});
it('renders an Embed component', () => {
expect(wrapper.find(MetricEmbed).exists()).toBe(true);
});
it('passes the correct props to the Embed component', () => {
expect(wrapper.find(MetricEmbed).props()).toEqual(singleEmbedProps());
});
it('adds the monitoring dashboard module', () => {
expect(store.dispatch).toHaveBeenCalledWith(addModuleAction, 'monitoringDashboard/0');
});
});
describe('dashboard metrics', () => {
beforeEach(() => {
metricsWithDataGetter.mockReturnValue([2]);
mountComponent();
});
it('passes the correct props to the dashboard Embed component', () => {
expect(wrapper.find(MetricEmbed).props()).toEqual(dashboardEmbedProps());
});
it('adds the monitoring dashboard module', () => {
expect(store.dispatch).toHaveBeenCalledWith(addModuleAction, 'monitoringDashboard/0');
});
});
describe('multiple metrics', () => {
beforeEach(() => {
metricsWithDataGetter.mockReturnValue([1, 1]);
mountComponent({ urls: [TEST_HOST, TEST_HOST] });
});
it('creates Embed components', () => {
expect(wrapper.findAll(MetricEmbed)).toHaveLength(2);
});
it('passes the correct props to the Embed components', () => {
expect(wrapper.findAll(MetricEmbed).wrappers.map(item => item.props())).toEqual(
multipleEmbedProps(),
);
});
it('adds multiple monitoring dashboard modules', () => {
expect(store.dispatch).toHaveBeenCalledWith(addModuleAction, 'monitoringDashboard/0');
expect(store.dispatch).toHaveBeenCalledWith(addModuleAction, 'monitoringDashboard/1');
});
});
describe('button text', () => {
it('has a singular label when there is one embed', () => {
metricsWithDataGetter.mockReturnValue([1]);
mountComponent({ shallow: false, stubs: { MetricEmbed: '<div />' } });
expect(wrapper.find(GlButton).text()).toBe('Hide chart');
});
it('has a plural label when there are multiple embeds', () => {
metricsWithDataGetter.mockReturnValue([2]);
mountComponent({ shallow: false, stubs: { MetricEmbed: '<div />' } });
expect(wrapper.find(GlButton).text()).toBe('Hide charts');
});
});
});
......@@ -2,20 +2,20 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
import { TEST_HOST } from 'helpers/test_constants';
import Embed from '~/monitoring/components/embed.vue';
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
import { groups, initialState, metricsData, metricsWithData } from './mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Embed', () => {
describe('MetricEmbed', () => {
let wrapper;
let store;
let actions;
let metricsWithDataGetter;
function mountComponent() {
wrapper = shallowMount(Embed, {
wrapper = shallowMount(MetricEmbed, {
localVue,
store,
propsData: {
......
import { TEST_HOST } from 'helpers/test_constants';
export const metricsWithData = ['15_metric_a', '16_metric_b'];
export const groups = [
......@@ -52,3 +54,34 @@ export const initialState = () => ({
},
useDashboardEndpoint: true,
});
export const initialEmbedGroupState = () => ({
modules: [],
});
export const singleEmbedProps = () => ({
dashboardUrl: TEST_HOST,
containerClass: 'col-lg-12',
namespace: 'monitoringDashboard/0',
});
export const dashboardEmbedProps = () => ({
dashboardUrl: TEST_HOST,
containerClass: 'col-lg-6',
namespace: 'monitoringDashboard/0',
});
export const multipleEmbedProps = () => [
{
dashboardUrl: TEST_HOST,
containerClass: 'col-lg-6',
namespace: 'monitoringDashboard/0',
},
{
dashboardUrl: TEST_HOST,
containerClass: 'col-lg-6',
namespace: 'monitoringDashboard/1',
},
];
export const addModuleAction = 'embedGroup/addModule';
......@@ -8,8 +8,17 @@ import PanelType from '~/monitoring/components/panel_type.vue';
import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
import TimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
import AnomalyChart from '~/monitoring/components/charts/anomaly.vue';
import { anomalyMockGraphData, graphDataPrometheusQueryRange } from 'jest/monitoring/mock_data';
import { createStore } from '~/monitoring/stores';
import {
anomalyMockGraphData,
graphDataPrometheusQueryRange,
mockLogsHref,
mockLogsPath,
mockNamespace,
mockNamespacedData,
mockTimeRange,
} from 'jest/monitoring/mock_data';
import { createStore, monitoringDashboard } from '~/monitoring/stores';
import { createStore as createEmbedGroupStore } from '~/monitoring/stores/embed_group';
global.IS_EE = true;
global.URL.createObjectURL = jest.fn();
......@@ -29,6 +38,7 @@ describe('Panel Type component', () => {
const exampleText = 'example_text';
const findCopyLink = () => wrapper.find({ ref: 'copyChartLink' });
const findTimeChart = () => wrapper.find({ ref: 'timeChart' });
const createWrapper = props => {
wrapper = shallowMount(PanelType, {
......@@ -99,8 +109,6 @@ describe('Panel Type component', () => {
});
describe('when graph data is available', () => {
const findTimeChart = () => wrapper.find({ ref: 'timeChart' });
beforeEach(() => {
createWrapper({
graphData: graphDataPrometheusQueryRange,
......@@ -242,10 +250,6 @@ describe('Panel Type component', () => {
});
describe('View Logs dropdown item', () => {
const mockLogsPath = '/path/to/logs';
const mockTimeRange = { duration: { seconds: 120 } };
const findTimeChart = () => wrapper.find({ ref: 'timeChart' });
const findViewLogsLink = () => wrapper.find({ ref: 'viewLogsLink' });
beforeEach(() => {
......@@ -292,8 +296,7 @@ describe('Panel Type component', () => {
state.timeRange = mockTimeRange;
return wrapper.vm.$nextTick(() => {
const href = `${mockLogsPath}?duration_seconds=${mockTimeRange.duration.seconds}`;
expect(findViewLogsLink().attributes('href')).toMatch(href);
expect(findViewLogsLink().attributes('href')).toMatch(mockLogsHref);
});
});
......@@ -388,4 +391,53 @@ describe('Panel Type component', () => {
});
});
});
describe('when using dynamic modules', () => {
const { mockDeploymentData, mockProjectPath } = mockNamespacedData;
beforeEach(() => {
store = createEmbedGroupStore();
store.registerModule(mockNamespace, monitoringDashboard);
store.state.embedGroup.modules.push(mockNamespace);
wrapper = shallowMount(PanelType, {
propsData: {
graphData: graphDataPrometheusQueryRange,
namespace: mockNamespace,
},
store,
mocks,
});
});
it('handles namespaced time range and logs path state', () => {
store.state[mockNamespace].timeRange = mockTimeRange;
store.state[mockNamespace].logsPath = mockLogsPath;
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find({ ref: 'viewLogsLink' }).attributes().href).toBe(mockLogsHref);
});
});
it('handles namespaced deployment data state', () => {
store.state[mockNamespace].deploymentData = mockDeploymentData;
return wrapper.vm.$nextTick().then(() => {
expect(findTimeChart().props().deploymentData).toEqual(mockDeploymentData);
});
});
it('handles namespaced project path state', () => {
store.state[mockNamespace].projectPath = mockProjectPath;
return wrapper.vm.$nextTick().then(() => {
expect(findTimeChart().props().projectPath).toBe(mockProjectPath);
});
});
it('it renders a time series chart with no errors', () => {
expect(wrapper.find(TimeSeriesChart).isVueInstance()).toBe(true);
expect(wrapper.find(TimeSeriesChart).exists()).toBe(true);
});
});
});
......@@ -750,3 +750,20 @@ export const barMockData = {
},
],
};
export const baseNamespace = 'monitoringDashboard';
export const mockNamespace = `${baseNamespace}/1`;
export const mockNamespaces = [`${baseNamespace}/1`, `${baseNamespace}/2`];
export const mockTimeRange = { duration: { seconds: 120 } };
export const mockNamespacedData = {
mockDeploymentData: ['mockDeploymentData'],
mockProjectPath: '/mockProjectPath',
};
export const mockLogsPath = '/mockLogsPath';
export const mockLogsHref = `${mockLogsPath}?duration_seconds=${mockTimeRange.duration.seconds}`;
// import store from '~/monitoring/stores/embed_group';
import * as actions from '~/monitoring/stores/embed_group/actions';
import * as types from '~/monitoring/stores/embed_group/mutation_types';
import { mockNamespace } from '../../mock_data';
describe('Embed group actions', () => {
describe('addModule', () => {
it('adds a module to the store', () => {
const commit = jest.fn();
actions.addModule({ commit }, mockNamespace);
expect(commit).toHaveBeenCalledWith(types.ADD_MODULE, mockNamespace);
});
});
});
import { metricsWithData } from '~/monitoring/stores/embed_group/getters';
import { mockNamespaces } from '../../mock_data';
describe('Embed group getters', () => {
describe('metricsWithData', () => {
it('correctly sums the number of metrics with data', () => {
const mockMetric = {};
const state = {
modules: mockNamespaces,
};
const rootGetters = {
[`${mockNamespaces[0]}/metricsWithData`]: () => [mockMetric],
[`${mockNamespaces[1]}/metricsWithData`]: () => [mockMetric, mockMetric],
};
expect(metricsWithData(state, null, null, rootGetters)).toEqual([1, 2]);
});
});
});
import state from '~/monitoring/stores/embed_group/state';
import mutations from '~/monitoring/stores/embed_group/mutations';
import * as types from '~/monitoring/stores/embed_group/mutation_types';
import { mockNamespace } from '../../mock_data';
describe('Embed group mutations', () => {
describe('ADD_MODULE', () => {
it('should add a module', () => {
const stateCopy = state();
mutations[types.ADD_MODULE](stateCopy, mockNamespace);
expect(stateCopy.modules).toEqual([mockNamespace]);
});
});
});
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