summaryrefslogtreecommitdiff
path: root/spec/frontend/monitoring/components
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/monitoring/components')
-rw-r--r--spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap25
-rw-r--r--spec/frontend/monitoring/components/alert_widget_form_spec.js220
-rw-r--r--spec/frontend/monitoring/components/charts/single_stat_spec.js14
-rw-r--r--spec/frontend/monitoring/components/charts/time_series_spec.js43
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_spec.js (renamed from spec/frontend/monitoring/components/panel_type_spec.js)266
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js549
-rw-r--r--spec/frontend/monitoring/components/dashboard_template_spec.js15
-rw-r--r--spec/frontend/monitoring/components/dashboard_url_time_spec.js2
-rw-r--r--spec/frontend/monitoring/components/dashboards_dropdown_spec.js127
-rw-r--r--spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js26
-rw-r--r--spec/frontend/monitoring/components/embeds/metric_embed_spec.js10
-rw-r--r--spec/frontend/monitoring/components/variables/custom_variable_spec.js52
-rw-r--r--spec/frontend/monitoring/components/variables/text_variable_spec.js59
-rw-r--r--spec/frontend/monitoring/components/variables_section_spec.js126
14 files changed, 1303 insertions, 231 deletions
diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
index 1906ad7c6ed..9be5fa72110 100644
--- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
+++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
@@ -16,7 +16,6 @@ exports[`Dashboard template matches the default snapshot 1`] = `
data-qa-selector="dashboards_filter_dropdown"
defaultbranch="master"
id="monitor-dashboards-dropdown"
- selecteddashboard="[object Object]"
toggle-class="dropdown-menu-toggle"
/>
</div>
@@ -72,7 +71,7 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<date-time-picker-stub
class="flex-grow-1 show-last-dropdown"
customenabled="true"
- data-qa-selector="show_last_dropdown"
+ data-qa-selector="range_picker_dropdown"
options="[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]"
value="[object Object]"
/>
@@ -101,6 +100,26 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<div
class="d-sm-flex"
>
+ <div
+ class="mb-2 mr-2 d-flex"
+ >
+ <div
+ class="flex-grow-1"
+ title="Star dashboard"
+ >
+ <gl-deprecated-button-stub
+ class="w-100"
+ size="md"
+ variant="default"
+ >
+ <gl-icon-stub
+ name="star-o"
+ size="16"
+ />
+ </gl-deprecated-button-stub>
+ </div>
+ </div>
+
<!---->
<!---->
@@ -111,6 +130,8 @@ exports[`Dashboard template matches the default snapshot 1`] = `
</div>
</div>
+ <!---->
+
<empty-state-stub
clusterspath="/path/to/clusters"
documentationpath="/path/to/docs"
diff --git a/spec/frontend/monitoring/components/alert_widget_form_spec.js b/spec/frontend/monitoring/components/alert_widget_form_spec.js
new file mode 100644
index 00000000000..a8416216a94
--- /dev/null
+++ b/spec/frontend/monitoring/components/alert_widget_form_spec.js
@@ -0,0 +1,220 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLink } from '@gitlab/ui';
+import AlertWidgetForm from '~/monitoring/components/alert_widget_form.vue';
+import ModalStub from '../stubs/modal_stub';
+
+describe('AlertWidgetForm', () => {
+ let wrapper;
+
+ const metricId = '8';
+ const alertPath = 'alert';
+ const relevantQueries = [{ metricId, alert_path: alertPath, label: 'alert-label' }];
+ const dataTrackingOptions = {
+ create: { action: 'click_button', label: 'create_alert' },
+ delete: { action: 'click_button', label: 'delete_alert' },
+ update: { action: 'click_button', label: 'update_alert' },
+ };
+
+ const defaultProps = {
+ disabled: false,
+ relevantQueries,
+ modalId: 'alert-modal-1',
+ };
+
+ const propsWithAlertData = {
+ ...defaultProps,
+ alertsToManage: {
+ alert: { alert_path: alertPath, operator: '<', threshold: 5, metricId },
+ },
+ configuredAlert: metricId,
+ };
+
+ function createComponent(props = {}) {
+ const propsData = {
+ ...defaultProps,
+ ...props,
+ };
+
+ wrapper = shallowMount(AlertWidgetForm, {
+ propsData,
+ stubs: {
+ GlModal: ModalStub,
+ },
+ });
+ }
+
+ const modal = () => wrapper.find(ModalStub);
+ const modalTitle = () => modal().attributes('title');
+ const submitButton = () => modal().find(GlLink);
+ const submitButtonTrackingOpts = () =>
+ JSON.parse(submitButton().attributes('data-tracking-options'));
+ const e = {
+ preventDefault: jest.fn(),
+ };
+
+ beforeEach(() => {
+ e.preventDefault.mockReset();
+ });
+
+ afterEach(() => {
+ if (wrapper) wrapper.destroy();
+ });
+
+ it('disables the form when disabled prop is set', () => {
+ createComponent({ disabled: true });
+
+ expect(modal().attributes('ok-disabled')).toBe('true');
+ });
+
+ it('disables the form if no query is selected', () => {
+ createComponent();
+
+ expect(modal().attributes('ok-disabled')).toBe('true');
+ });
+
+ it('shows correct title and button text', () => {
+ expect(modalTitle()).toBe('Add alert');
+ expect(submitButton().text()).toBe('Add');
+ });
+
+ it('sets tracking options for create alert', () => {
+ expect(submitButtonTrackingOpts()).toEqual(dataTrackingOptions.create);
+ });
+
+ it('emits a "create" event when form submitted without existing alert', () => {
+ createComponent();
+
+ wrapper.vm.selectQuery('9');
+ wrapper.setData({
+ threshold: 900,
+ });
+
+ wrapper.vm.handleSubmit(e);
+
+ expect(wrapper.emitted().create[0]).toEqual([
+ {
+ alert: undefined,
+ operator: '>',
+ threshold: 900,
+ prometheus_metric_id: '9',
+ },
+ ]);
+ expect(e.preventDefault).toHaveBeenCalledTimes(1);
+ });
+
+ it('resets form when modal is dismissed (hidden)', () => {
+ createComponent();
+
+ wrapper.vm.selectQuery('9');
+ wrapper.vm.selectQuery('>');
+ wrapper.setData({
+ threshold: 800,
+ });
+
+ modal().vm.$emit('hidden');
+
+ expect(wrapper.vm.selectedAlert).toEqual({});
+ expect(wrapper.vm.operator).toBe(null);
+ expect(wrapper.vm.threshold).toBe(null);
+ expect(wrapper.vm.prometheusMetricId).toBe(null);
+ });
+
+ it('sets selectedAlert to the provided configuredAlert on modal show', () => {
+ createComponent(propsWithAlertData);
+
+ modal().vm.$emit('shown');
+
+ expect(wrapper.vm.selectedAlert).toEqual(propsWithAlertData.alertsToManage[alertPath]);
+ });
+
+ it('sets selectedAlert to the first relevantQueries if there is only one option on modal show', () => {
+ createComponent({
+ ...propsWithAlertData,
+ configuredAlert: '',
+ });
+
+ modal().vm.$emit('shown');
+
+ expect(wrapper.vm.selectedAlert).toEqual(propsWithAlertData.alertsToManage[alertPath]);
+ });
+
+ it('does not set selectedAlert to the first relevantQueries if there is more than one option on modal show', () => {
+ createComponent({
+ relevantQueries: [
+ {
+ metricId: '8',
+ alertPath: 'alert',
+ label: 'alert-label',
+ },
+ {
+ metricId: '9',
+ alertPath: 'alert',
+ label: 'alert-label',
+ },
+ ],
+ });
+
+ modal().vm.$emit('shown');
+
+ expect(wrapper.vm.selectedAlert).toEqual({});
+ });
+
+ describe('with existing alert', () => {
+ beforeEach(() => {
+ createComponent(propsWithAlertData);
+
+ wrapper.vm.selectQuery(metricId);
+ });
+
+ it('sets tracking options for delete alert', () => {
+ expect(submitButtonTrackingOpts()).toEqual(dataTrackingOptions.delete);
+ });
+
+ it('updates button text', () => {
+ expect(modalTitle()).toBe('Edit alert');
+ expect(submitButton().text()).toBe('Delete');
+ });
+
+ it('emits "delete" event when form values unchanged', () => {
+ wrapper.vm.handleSubmit(e);
+
+ expect(wrapper.emitted().delete[0]).toEqual([
+ {
+ alert: 'alert',
+ operator: '<',
+ threshold: 5,
+ prometheus_metric_id: '8',
+ },
+ ]);
+ expect(e.preventDefault).toHaveBeenCalledTimes(1);
+ });
+
+ it('emits "update" event when form changed', () => {
+ wrapper.setData({
+ threshold: 11,
+ });
+
+ wrapper.vm.handleSubmit(e);
+
+ expect(wrapper.emitted().update[0]).toEqual([
+ {
+ alert: 'alert',
+ operator: '<',
+ threshold: 11,
+ prometheus_metric_id: '8',
+ },
+ ]);
+ expect(e.preventDefault).toHaveBeenCalledTimes(1);
+ });
+
+ it('sets tracking options for update alert', () => {
+ wrapper.setData({
+ threshold: 11,
+ });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(submitButtonTrackingOpts()).toEqual(dataTrackingOptions.update);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/charts/single_stat_spec.js b/spec/frontend/monitoring/components/charts/single_stat_spec.js
index fb0682d0338..9cc5970da82 100644
--- a/spec/frontend/monitoring/components/charts/single_stat_spec.js
+++ b/spec/frontend/monitoring/components/charts/single_stat_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import SingleStatChart from '~/monitoring/components/charts/single_stat.vue';
-import { graphDataPrometheusQuery } from '../../mock_data';
+import { singleStatMetricsResult } from '../../mock_data';
describe('Single Stat Chart component', () => {
let singleStatChart;
@@ -8,7 +8,7 @@ describe('Single Stat Chart component', () => {
beforeEach(() => {
singleStatChart = shallowMount(SingleStatChart, {
propsData: {
- graphData: graphDataPrometheusQuery,
+ graphData: singleStatMetricsResult,
},
});
});
@@ -26,7 +26,7 @@ describe('Single Stat Chart component', () => {
it('should change the value representation to a percentile one', () => {
singleStatChart.setProps({
graphData: {
- ...graphDataPrometheusQuery,
+ ...singleStatMetricsResult,
maxValue: 120,
},
});
@@ -37,7 +37,7 @@ describe('Single Stat Chart component', () => {
it('should display NaN for non numeric maxValue values', () => {
singleStatChart.setProps({
graphData: {
- ...graphDataPrometheusQuery,
+ ...singleStatMetricsResult,
maxValue: 'not a number',
},
});
@@ -48,13 +48,13 @@ describe('Single Stat Chart component', () => {
it('should display NaN for missing query values', () => {
singleStatChart.setProps({
graphData: {
- ...graphDataPrometheusQuery,
+ ...singleStatMetricsResult,
metrics: [
{
- ...graphDataPrometheusQuery.metrics[0],
+ ...singleStatMetricsResult.metrics[0],
result: [
{
- ...graphDataPrometheusQuery.metrics[0].result[0],
+ ...singleStatMetricsResult.metrics[0].result[0],
value: [''],
},
],
diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js
index 5ac716b0c63..7d5a08bc4a1 100644
--- a/spec/frontend/monitoring/components/charts/time_series_spec.js
+++ b/spec/frontend/monitoring/components/charts/time_series_spec.js
@@ -1,4 +1,4 @@
-import { mount } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
import { setTestTimeout } from 'helpers/timeout';
import { GlLink } from '@gitlab/ui';
import { TEST_HOST } from 'jest/helpers/test_constants';
@@ -11,6 +11,7 @@ import {
import { cloneDeep } from 'lodash';
import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper';
import { createStore } from '~/monitoring/stores';
+import { panelTypes, chartHeight } from '~/monitoring/constants';
import TimeSeries from '~/monitoring/components/charts/time_series.vue';
import * as types from '~/monitoring/stores/mutation_types';
import { deploymentData, mockProjectDir, annotationsData } from '../../mock_data';
@@ -39,10 +40,10 @@ describe('Time series component', () => {
let mockGraphData;
let store;
- const makeTimeSeriesChart = (graphData, type) =>
- mount(TimeSeries, {
+ const createWrapper = (graphData = mockGraphData, mountingMethod = shallowMount) =>
+ mountingMethod(TimeSeries, {
propsData: {
- graphData: { ...graphData, type },
+ graphData,
deploymentData: store.state.monitoringDashboard.deploymentData,
annotations: store.state.monitoringDashboard.annotations,
projectPath: `${TEST_HOST}${mockProjectDir}`,
@@ -79,9 +80,9 @@ describe('Time series component', () => {
const findChart = () => timeSeriesChart.find({ ref: 'chart' });
- beforeEach(done => {
- timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart');
- timeSeriesChart.vm.$nextTick(done);
+ beforeEach(() => {
+ timeSeriesChart = createWrapper(mockGraphData, mount);
+ return timeSeriesChart.vm.$nextTick();
});
it('allows user to override max value label text using prop', () => {
@@ -100,6 +101,21 @@ describe('Time series component', () => {
});
});
+ it('chart sets a default height', () => {
+ const wrapper = createWrapper();
+ expect(wrapper.props('height')).toBe(chartHeight);
+ });
+
+ it('chart has a configurable height', () => {
+ const mockHeight = 599;
+ const wrapper = createWrapper();
+
+ wrapper.setProps({ height: mockHeight });
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.props('height')).toBe(mockHeight);
+ });
+ });
+
describe('events', () => {
describe('datazoom', () => {
let eChartMock;
@@ -125,7 +141,7 @@ describe('Time series component', () => {
}),
};
- timeSeriesChart = makeTimeSeriesChart(mockGraphData);
+ timeSeriesChart = createWrapper(mockGraphData, mount);
timeSeriesChart.vm.$nextTick(() => {
findChart().vm.$emit('created', eChartMock);
done();
@@ -535,11 +551,11 @@ describe('Time series component', () => {
describe('wrapped components', () => {
const glChartComponents = [
{
- chartType: 'area-chart',
+ chartType: panelTypes.AREA_CHART,
component: GlAreaChart,
},
{
- chartType: 'line-chart',
+ chartType: panelTypes.LINE_CHART,
component: GlLineChart,
},
];
@@ -550,7 +566,10 @@ describe('Time series component', () => {
const findChartComponent = () => timeSeriesAreaChart.find(dynamicComponent.component);
beforeEach(done => {
- timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType);
+ timeSeriesAreaChart = createWrapper(
+ { ...mockGraphData, type: dynamicComponent.chartType },
+ mount,
+ );
timeSeriesAreaChart.vm.$nextTick(done);
});
@@ -632,7 +651,7 @@ describe('Time series component', () => {
Object.assign(metric, { result: metricResultStatus.result }),
);
- timeSeriesChart = makeTimeSeriesChart(graphData, 'area-chart');
+ timeSeriesChart = createWrapper({ ...graphData, type: 'area-chart' }, mount);
timeSeriesChart.vm.$nextTick(done);
});
diff --git a/spec/frontend/monitoring/components/panel_type_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js
index 819b5235284..f8c9bd56721 100644
--- a/spec/frontend/monitoring/components/panel_type_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js
@@ -1,13 +1,13 @@
+import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { setTestTimeout } from 'helpers/timeout';
import invalidUrl from '~/lib/utils/invalid_url';
import axios from '~/lib/utils/axios_utils';
+import { GlDropdownItem } from '@gitlab/ui';
+import AlertWidget from '~/monitoring/components/alert_widget.vue';
-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 DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import {
anomalyMockGraphData,
mockLogsHref,
@@ -15,8 +15,23 @@ import {
mockNamespace,
mockNamespacedData,
mockTimeRange,
+ singleStatMetricsResult,
+ graphDataPrometheusQueryRangeMultiTrack,
+ barMockData,
+ propsData,
} from '../mock_data';
+import { panelTypes } from '~/monitoring/constants';
+
+import MonitorEmptyChart from '~/monitoring/components/charts/empty_chart.vue';
+import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
+import MonitorAnomalyChart from '~/monitoring/components/charts/anomaly.vue';
+import MonitorSingleStatChart from '~/monitoring/components/charts/single_stat.vue';
+import MonitorHeatmapChart from '~/monitoring/components/charts/heatmap.vue';
+import MonitorColumnChart from '~/monitoring/components/charts/column.vue';
+import MonitorBarChart from '~/monitoring/components/charts/bar.vue';
+import MonitorStackedColumnChart from '~/monitoring/components/charts/stacked_column.vue';
+
import { graphData, graphDataEmpty } from '../fixture_data';
import { createStore, monitoringDashboard } from '~/monitoring/stores';
import { createStore as createEmbedGroupStore } from '~/monitoring/stores/embed_group';
@@ -29,7 +44,7 @@ const mocks = {
},
};
-describe('Panel Type component', () => {
+describe('Dashboard Panel', () => {
let axiosMock;
let store;
let state;
@@ -38,18 +53,20 @@ describe('Panel Type component', () => {
const exampleText = 'example_text';
const findCopyLink = () => wrapper.find({ ref: 'copyChartLink' });
- const findTimeChart = () => wrapper.find({ ref: 'timeChart' });
+ const findTimeChart = () => wrapper.find({ ref: 'timeSeriesChart' });
const findTitle = () => wrapper.find({ ref: 'graphTitle' });
const findContextualMenu = () => wrapper.find({ ref: 'contextualMenu' });
- const createWrapper = props => {
- wrapper = shallowMount(PanelType, {
+ const createWrapper = (props, options) => {
+ wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
+ settingsPath: propsData.settingsPath,
...props,
},
store,
mocks,
+ ...options,
});
};
@@ -66,6 +83,22 @@ describe('Panel Type component', () => {
axiosMock.reset();
});
+ describe('Renders slots', () => {
+ it('renders "topLeft" slot', () => {
+ createWrapper(
+ {},
+ {
+ slots: {
+ topLeft: `<div class="top-left-content">OK</div>`,
+ },
+ },
+ );
+
+ expect(wrapper.find('.top-left-content').exists()).toBe(true);
+ expect(wrapper.find('.top-left-content').text()).toBe('OK');
+ });
+ });
+
describe('When no graphData is available', () => {
beforeEach(() => {
createWrapper({
@@ -77,27 +110,54 @@ describe('Panel Type component', () => {
wrapper.destroy();
});
- describe('Empty Chart component', () => {
- it('renders the chart title', () => {
- expect(findTitle().text()).toBe(graphDataEmpty.title);
- });
+ it('renders the chart title', () => {
+ expect(findTitle().text()).toBe(graphDataEmpty.title);
+ });
- it('renders the no download csv link', () => {
- expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(false);
- });
+ it('renders no download csv link', () => {
+ expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(false);
+ });
- it('does not contain graph widgets', () => {
- expect(findContextualMenu().exists()).toBe(false);
- });
+ it('does not contain graph widgets', () => {
+ expect(findContextualMenu().exists()).toBe(false);
+ });
- it('is a Vue instance', () => {
- expect(wrapper.find(EmptyChart).exists()).toBe(true);
- expect(wrapper.find(EmptyChart).isVueInstance()).toBe(true);
+ it('The Empty Chart component is rendered and is a Vue instance', () => {
+ expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true);
+ expect(wrapper.find(MonitorEmptyChart).isVueInstance()).toBe(true);
+ });
+ });
+
+ describe('When graphData is null', () => {
+ beforeEach(() => {
+ createWrapper({
+ graphData: null,
});
});
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders no chart title', () => {
+ expect(findTitle().text()).toBe('');
+ });
+
+ it('renders no download csv link', () => {
+ expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(false);
+ });
+
+ it('does not contain graph widgets', () => {
+ expect(findContextualMenu().exists()).toBe(false);
+ });
+
+ it('The Empty Chart component is rendered and is a Vue instance', () => {
+ expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true);
+ expect(wrapper.find(MonitorEmptyChart).isVueInstance()).toBe(true);
+ });
});
- describe('when graph data is available', () => {
+ describe('When graphData is available', () => {
beforeEach(() => {
createWrapper();
});
@@ -134,34 +194,54 @@ describe('Panel Type component', () => {
});
});
- describe('Time Series Chart panel type', () => {
- it('is rendered', () => {
- expect(wrapper.find(TimeSeriesChart).isVueInstance()).toBe(true);
- expect(wrapper.find(TimeSeriesChart).exists()).toBe(true);
- });
+ it('includes a default group id', () => {
+ expect(wrapper.vm.groupId).toBe('dashboard-panel');
+ });
+
+ describe('Supports different panel types', () => {
+ const dataWithType = type => {
+ return {
+ ...graphData,
+ type,
+ };
+ };
- it('includes a default group id', () => {
- expect(wrapper.vm.groupId).toBe('panel-type-chart');
+ it('empty chart is rendered for empty results', () => {
+ createWrapper({ graphData: graphDataEmpty });
+ expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true);
+ expect(wrapper.find(MonitorEmptyChart).isVueInstance()).toBe(true);
});
- });
- describe('Anomaly Chart panel type', () => {
- beforeEach(() => {
- wrapper.setProps({
- graphData: anomalyMockGraphData,
- });
- return wrapper.vm.$nextTick();
+ it('area chart is rendered by default', () => {
+ createWrapper();
+ expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(true);
+ expect(wrapper.find(MonitorTimeSeriesChart).isVueInstance()).toBe(true);
});
- it('is rendered with an anomaly chart', () => {
- expect(wrapper.find(AnomalyChart).isVueInstance()).toBe(true);
- expect(wrapper.find(AnomalyChart).exists()).toBe(true);
+ it.each`
+ data | component
+ ${dataWithType(panelTypes.AREA_CHART)} | ${MonitorTimeSeriesChart}
+ ${dataWithType(panelTypes.LINE_CHART)} | ${MonitorTimeSeriesChart}
+ ${anomalyMockGraphData} | ${MonitorAnomalyChart}
+ ${dataWithType(panelTypes.COLUMN)} | ${MonitorColumnChart}
+ ${dataWithType(panelTypes.STACKED_COLUMN)} | ${MonitorStackedColumnChart}
+ ${singleStatMetricsResult} | ${MonitorSingleStatChart}
+ ${graphDataPrometheusQueryRangeMultiTrack} | ${MonitorHeatmapChart}
+ ${barMockData} | ${MonitorBarChart}
+ `('wrapps a $data.type component binding attributes', ({ data, component }) => {
+ const attrs = { attr1: 'attr1Value', attr2: 'attr2Value' };
+ createWrapper({ graphData: data }, { attrs });
+
+ expect(wrapper.find(component).exists()).toBe(true);
+ expect(wrapper.find(component).isVueInstance()).toBe(true);
+ expect(wrapper.find(component).attributes()).toMatchObject(attrs);
});
});
});
describe('Edit custom metric dropdown item', () => {
const findEditCustomMetricLink = () => wrapper.find({ ref: 'editMetricLink' });
+ const mockEditPath = '/root/kubernetes-gke-project/prometheus/metrics/23/edit';
beforeEach(() => {
createWrapper();
@@ -180,7 +260,7 @@ describe('Panel Type component', () => {
metrics: [
{
...graphData.metrics[0],
- edit_path: '/root/kubernetes-gke-project/prometheus/metrics/23/edit',
+ edit_path: mockEditPath,
},
],
},
@@ -189,10 +269,11 @@ describe('Panel Type component', () => {
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().exists()).toBe(true);
expect(findEditCustomMetricLink().text()).toBe('Edit metric');
+ expect(findEditCustomMetricLink().attributes('href')).toBe(mockEditPath);
});
});
- it('shows an "Edit metrics" link for a panel with multiple metrics', () => {
+ it('shows an "Edit metrics" link pointing to settingsPath for a panel with multiple metrics', () => {
wrapper.setProps({
graphData: {
...graphData,
@@ -211,6 +292,7 @@ describe('Panel Type component', () => {
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().text()).toBe('Edit metrics');
+ expect(findEditCustomMetricLink().attributes('href')).toBe(propsData.settingsPath);
});
});
});
@@ -294,10 +376,6 @@ describe('Panel Type component', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('sets clipboard text on the dropdown', () => {
expect(findCopyLink().exists()).toBe(true);
expect(findCopyLink().element.dataset.clipboardText).toBe(clipboardText);
@@ -314,11 +392,24 @@ describe('Panel Type component', () => {
});
});
+ describe('when cliboard data is not available', () => {
+ it('there is no "copy to clipboard" link for a null value', () => {
+ createWrapper({ clipboardText: null });
+ expect(findCopyLink().exists()).toBe(false);
+ });
+
+ it('there is no "copy to clipboard" link for an empty value', () => {
+ createWrapper({ clipboardText: '' });
+ expect(findCopyLink().exists()).toBe(false);
+ });
+ });
+
describe('when downloading metrics data as CSV', () => {
beforeEach(() => {
- wrapper = shallowMount(PanelType, {
+ wrapper = shallowMount(DashboardPanel, {
propsData: {
clipboardText: exampleText,
+ settingsPath: propsData.settingsPath,
graphData: {
y_label: 'metric',
...graphData,
@@ -365,9 +456,10 @@ describe('Panel Type component', () => {
store.registerModule(mockNamespace, monitoringDashboard);
store.state.embedGroup.modules.push(mockNamespace);
- wrapper = shallowMount(PanelType, {
+ wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
+ settingsPath: propsData.settingsPath,
namespace: mockNamespace,
},
store,
@@ -401,8 +493,84 @@ describe('Panel Type component', () => {
});
it('it renders a time series chart with no errors', () => {
- expect(wrapper.find(TimeSeriesChart).isVueInstance()).toBe(true);
- expect(wrapper.find(TimeSeriesChart).exists()).toBe(true);
+ expect(wrapper.find(MonitorTimeSeriesChart).isVueInstance()).toBe(true);
+ expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(true);
+ });
+ });
+
+ describe('Expand to full screen', () => {
+ const findExpandBtn = () => wrapper.find({ ref: 'expandBtn' });
+
+ describe('when there is no @expand listener', () => {
+ it('does not show `View full screen` option', () => {
+ createWrapper();
+ expect(findExpandBtn().exists()).toBe(false);
+ });
+ });
+
+ describe('when there is an @expand listener', () => {
+ beforeEach(() => {
+ createWrapper({}, { listeners: { expand: () => {} } });
+ });
+
+ it('shows the `expand` option', () => {
+ expect(findExpandBtn().exists()).toBe(true);
+ });
+
+ it('emits the `expand` event', () => {
+ const preventDefault = jest.fn();
+ findExpandBtn().vm.$emit('click', { preventDefault });
+ expect(wrapper.emitted('expand')).toHaveLength(1);
+ expect(preventDefault).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('panel alerts', () => {
+ const setMetricsSavedToDb = val =>
+ monitoringDashboard.getters.metricsSavedToDb.mockReturnValue(val);
+ const findAlertsWidget = () => wrapper.find(AlertWidget);
+ const findMenuItemAlert = () =>
+ wrapper.findAll(GlDropdownItem).filter(i => i.text() === 'Alerts');
+
+ beforeEach(() => {
+ jest.spyOn(monitoringDashboard.getters, 'metricsSavedToDb').mockReturnValue([]);
+
+ store = new Vuex.Store({
+ modules: {
+ monitoringDashboard,
+ },
+ });
+
+ createWrapper();
+ });
+
+ describe.each`
+ desc | metricsSavedToDb | props | isShown
+ ${'with permission and no metrics in db'} | ${[]} | ${{}} | ${false}
+ ${'with permission and related metrics in db'} | ${[graphData.metrics[0].metricId]} | ${{}} | ${true}
+ ${'without permission and related metrics in db'} | ${[graphData.metrics[0].metricId]} | ${{ prometheusAlertsAvailable: false }} | ${false}
+ ${'with permission and unrelated metrics in db'} | ${['another_metric_id']} | ${{}} | ${false}
+ `('$desc', ({ metricsSavedToDb, isShown, props }) => {
+ const showsDesc = isShown ? 'shows' : 'does not show';
+
+ beforeEach(() => {
+ setMetricsSavedToDb(metricsSavedToDb);
+ createWrapper({
+ alertsEndpoint: '/endpoint',
+ prometheusAlertsAvailable: true,
+ ...props,
+ });
+ return wrapper.vm.$nextTick();
+ });
+
+ it(`${showsDesc} alert widget`, () => {
+ expect(findAlertsWidget().exists()).toBe(isShown);
+ });
+
+ it(`${showsDesc} alert configuration`, () => {
+ expect(findMenuItemAlert().exists()).toBe(isShown);
+ });
});
});
});
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index 8b6ee9b3bf6..b2c9fe93cde 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -1,6 +1,8 @@
import { shallowMount, mount } from '@vue/test-utils';
import Tracking from '~/tracking';
-import { GlModal, GlDropdownItem, GlDeprecatedButton } from '@gitlab/ui';
+import { ESC_KEY, ESC_KEY_IE11 } from '~/lib/utils/keys';
+import { GlModal, GlDropdownItem, GlDeprecatedButton, GlIcon } from '@gitlab/ui';
+import { objectToQuery } from '~/lib/utils/url_utility';
import VueDraggable from 'vuedraggable';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
@@ -11,13 +13,23 @@ import Dashboard from '~/monitoring/components/dashboard.vue';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
+import EmptyState from '~/monitoring/components/empty_state.vue';
import GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
-import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
+import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
-import { setupStoreWithDashboard, setMetricResult, setupStoreWithData } from '../store_utils';
+import {
+ setupAllDashboards,
+ setupStoreWithDashboard,
+ setMetricResult,
+ setupStoreWithData,
+ setupStoreWithVariable,
+} from '../store_utils';
import { environmentData, dashboardGitResponse, propsData } from '../mock_data';
import { metricsDashboardViewModel, metricsDashboardPanelCount } from '../fixture_data';
+import createFlash from '~/flash';
+
+jest.mock('~/flash');
describe('Dashboard', () => {
let store;
@@ -27,15 +39,12 @@ describe('Dashboard', () => {
const findEnvironmentsDropdown = () => wrapper.find({ ref: 'monitorEnvironmentsDropdown' });
const findAllEnvironmentsDropdownItems = () => findEnvironmentsDropdown().findAll(GlDropdownItem);
const setSearchTerm = searchTerm => {
- wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm);
+ store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm);
};
const createShallowWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(Dashboard, {
propsData: { ...propsData, ...props },
- methods: {
- fetchData: jest.fn(),
- },
store,
...options,
});
@@ -44,10 +53,8 @@ describe('Dashboard', () => {
const createMountedWrapper = (props = {}, options = {}) => {
wrapper = mount(Dashboard, {
propsData: { ...propsData, ...props },
- methods: {
- fetchData: jest.fn(),
- },
store,
+ stubs: ['graph-group', 'dashboard-panel'],
...options,
});
};
@@ -55,19 +62,18 @@ describe('Dashboard', () => {
beforeEach(() => {
store = createStore();
mock = new MockAdapter(axios);
+ jest.spyOn(store, 'dispatch').mockResolvedValue();
});
afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- wrapper = null;
- }
mock.restore();
+ if (store.dispatch.mockReset) {
+ store.dispatch.mockReset();
+ }
});
describe('no metrics are available yet', () => {
beforeEach(() => {
- jest.spyOn(store, 'dispatch');
createShallowWrapper();
});
@@ -103,9 +109,7 @@ describe('Dashboard', () => {
describe('request information to the server', () => {
it('calls to set time range and fetch data', () => {
- jest.spyOn(store, 'dispatch');
-
- createShallowWrapper({ hasMetrics: true }, { methods: {} });
+ createShallowWrapper({ hasMetrics: true });
return wrapper.vm.$nextTick().then(() => {
expect(store.dispatch).toHaveBeenCalledWith(
@@ -118,20 +122,20 @@ describe('Dashboard', () => {
});
it('shows up a loading state', () => {
- createShallowWrapper({ hasMetrics: true }, { methods: {} });
+ store.state.monitoringDashboard.emptyState = 'loading';
+
+ createShallowWrapper({ hasMetrics: true });
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.emptyState).toEqual('loading');
+ expect(wrapper.find(EmptyState).exists()).toBe(true);
+ expect(wrapper.find(EmptyState).props('selectedState')).toBe('loading');
});
});
it('hides the group panels when showPanels is false', () => {
- createMountedWrapper(
- { hasMetrics: true, showPanels: false },
- { stubs: ['graph-group', 'panel-type'] },
- );
+ createMountedWrapper({ hasMetrics: true, showPanels: false });
- setupStoreWithData(wrapper.vm.$store);
+ setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.showEmptyState).toEqual(false);
@@ -142,9 +146,9 @@ describe('Dashboard', () => {
it('fetches the metrics data with proper time window', () => {
jest.spyOn(store, 'dispatch');
- createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
+ createMountedWrapper({ hasMetrics: true });
- wrapper.vm.$store.commit(
+ store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
environmentData,
);
@@ -155,11 +159,176 @@ describe('Dashboard', () => {
});
});
+ describe('when the URL contains a reference to a panel', () => {
+ let location;
+
+ const setSearch = search => {
+ window.location = { ...location, search };
+ };
+
+ beforeEach(() => {
+ location = window.location;
+ delete window.location;
+ });
+
+ afterEach(() => {
+ window.location = location;
+ });
+
+ it('when the URL points to a panel it expands', () => {
+ const panelGroup = metricsDashboardViewModel.panelGroups[0];
+ const panel = panelGroup.panels[0];
+
+ setSearch(
+ objectToQuery({
+ group: panelGroup.group,
+ title: panel.title,
+ y_label: panel.y_label,
+ }),
+ );
+
+ createMountedWrapper({ hasMetrics: true });
+ setupStoreWithData(store);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setExpandedPanel', {
+ group: panelGroup.group,
+ panel: expect.objectContaining({
+ title: panel.title,
+ y_label: panel.y_label,
+ }),
+ });
+ });
+ });
+
+ it('when the URL does not link to any panel, no panel is expanded', () => {
+ setSearch('');
+
+ createMountedWrapper({ hasMetrics: true });
+ setupStoreWithData(store);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(store.dispatch).not.toHaveBeenCalledWith(
+ 'monitoringDashboard/setExpandedPanel',
+ expect.anything(),
+ );
+ });
+ });
+
+ it('when the URL points to an incorrect panel it shows an error', () => {
+ const panelGroup = metricsDashboardViewModel.panelGroups[0];
+ const panel = panelGroup.panels[0];
+
+ setSearch(
+ objectToQuery({
+ group: panelGroup.group,
+ title: 'incorrect',
+ y_label: panel.y_label,
+ }),
+ );
+
+ createMountedWrapper({ hasMetrics: true });
+ setupStoreWithData(store);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(createFlash).toHaveBeenCalled();
+ expect(store.dispatch).not.toHaveBeenCalledWith(
+ 'monitoringDashboard/setExpandedPanel',
+ expect.anything(),
+ );
+ });
+ });
+ });
+
+ describe('when the panel is expanded', () => {
+ let group;
+ let panel;
+
+ const expandPanel = (mockGroup, mockPanel) => {
+ store.commit(`monitoringDashboard/${types.SET_EXPANDED_PANEL}`, {
+ group: mockGroup,
+ panel: mockPanel,
+ });
+ };
+
+ beforeEach(() => {
+ setupStoreWithData(store);
+
+ const { panelGroups } = store.state.monitoringDashboard.dashboard;
+ group = panelGroups[0].group;
+ [panel] = panelGroups[0].panels;
+
+ jest.spyOn(window.history, 'pushState').mockImplementation();
+ });
+
+ afterEach(() => {
+ window.history.pushState.mockRestore();
+ });
+
+ it('URL is updated with panel parameters', () => {
+ createMountedWrapper({ hasMetrics: true });
+ expandPanel(group, panel);
+
+ const expectedSearch = objectToQuery({
+ group,
+ title: panel.title,
+ y_label: panel.y_label,
+ });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(), // state
+ expect.any(String), // document title
+ expect.stringContaining(`${expectedSearch}`),
+ );
+ });
+ });
+
+ it('URL is updated with panel parameters and custom dashboard', () => {
+ const dashboard = 'dashboard.yml';
+
+ createMountedWrapper({ hasMetrics: true, currentDashboard: dashboard });
+ expandPanel(group, panel);
+
+ const expectedSearch = objectToQuery({
+ dashboard,
+ group,
+ title: panel.title,
+ y_label: panel.y_label,
+ });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(), // state
+ expect.any(String), // document title
+ expect.stringContaining(`${expectedSearch}`),
+ );
+ });
+ });
+
+ it('URL is updated with no parameters', () => {
+ expandPanel(group, panel);
+ createMountedWrapper({ hasMetrics: true });
+ expandPanel(null, null);
+
+ return wrapper.vm.$nextTick(() => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(), // state
+ expect.any(String), // document title
+ expect.not.stringMatching(/group|title|y_label/), // no panel params
+ );
+ });
+ });
+ });
+
describe('when all requests have been commited by the store', () => {
beforeEach(() => {
- createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
+ createMountedWrapper({ hasMetrics: true });
- setupStoreWithData(wrapper.vm.$store);
+ setupStoreWithData(store);
return wrapper.vm.$nextTick();
});
@@ -185,10 +354,89 @@ describe('Dashboard', () => {
});
});
+ describe('star dashboards', () => {
+ const findToggleStar = () => wrapper.find({ ref: 'toggleStarBtn' });
+ const findToggleStarIcon = () => findToggleStar().find(GlIcon);
+
+ beforeEach(() => {
+ createShallowWrapper();
+ setupAllDashboards(store);
+ });
+
+ it('toggle star button is shown', () => {
+ expect(findToggleStar().exists()).toBe(true);
+ expect(findToggleStar().props('disabled')).toBe(false);
+ });
+
+ it('toggle star button is disabled when starring is taking place', () => {
+ store.commit(`monitoringDashboard/${types.REQUEST_DASHBOARD_STARRING}`);
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findToggleStar().exists()).toBe(true);
+ expect(findToggleStar().props('disabled')).toBe(true);
+ });
+ });
+
+ describe('when the dashboard list is loaded', () => {
+ // Tooltip element should wrap directly
+ const getToggleTooltip = () => findToggleStar().element.parentElement.getAttribute('title');
+
+ beforeEach(() => {
+ setupAllDashboards(store);
+ jest.spyOn(store, 'dispatch');
+ });
+
+ it('dispatches a toggle star action', () => {
+ findToggleStar().vm.$emit('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'monitoringDashboard/toggleStarredValue',
+ undefined,
+ );
+ });
+ });
+
+ describe('when dashboard is not starred', () => {
+ beforeEach(() => {
+ store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
+ currentDashboard: dashboardGitResponse[0].path,
+ });
+ return wrapper.vm.$nextTick();
+ });
+
+ it('toggle star button shows "Star dashboard"', () => {
+ expect(getToggleTooltip()).toBe('Star dashboard');
+ });
+
+ it('toggle star button shows an unstarred state', () => {
+ expect(findToggleStarIcon().attributes('name')).toBe('star-o');
+ });
+ });
+
+ describe('when dashboard is starred', () => {
+ beforeEach(() => {
+ store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
+ currentDashboard: dashboardGitResponse[1].path,
+ });
+ return wrapper.vm.$nextTick();
+ });
+
+ it('toggle star button shows "Star dashboard"', () => {
+ expect(getToggleTooltip()).toBe('Unstar dashboard');
+ });
+
+ it('toggle star button shows a starred state', () => {
+ expect(findToggleStarIcon().attributes('name')).toBe('star');
+ });
+ });
+ });
+ });
+
it('hides the environments dropdown list when there is no environments', () => {
- createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
+ createMountedWrapper({ hasMetrics: true });
- setupStoreWithDashboard(wrapper.vm.$store);
+ setupStoreWithDashboard(store);
return wrapper.vm.$nextTick().then(() => {
expect(findAllEnvironmentsDropdownItems()).toHaveLength(0);
@@ -196,9 +444,9 @@ describe('Dashboard', () => {
});
it('renders the datetimepicker dropdown', () => {
- createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
+ createMountedWrapper({ hasMetrics: true });
- setupStoreWithData(wrapper.vm.$store);
+ setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(DateTimePicker).exists()).toBe(true);
@@ -206,9 +454,9 @@ describe('Dashboard', () => {
});
it('renders the refresh dashboard button', () => {
- createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
+ createMountedWrapper({ hasMetrics: true });
- setupStoreWithData(wrapper.vm.$store);
+ setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
const refreshBtn = wrapper.findAll({ ref: 'refreshDashboardBtn' });
@@ -218,14 +466,135 @@ describe('Dashboard', () => {
});
});
- describe('when one of the metrics is missing', () => {
+ describe('variables section', () => {
beforeEach(() => {
createShallowWrapper({ hasMetrics: true });
+ setupStoreWithData(store);
+ setupStoreWithVariable(store);
+
+ return wrapper.vm.$nextTick();
+ });
+
+ it('shows the variables section', () => {
+ expect(wrapper.vm.shouldShowVariablesSection).toBe(true);
+ });
+ });
+
+ describe('single panel expands to "full screen" mode', () => {
+ const findExpandedPanel = () => wrapper.find({ ref: 'expandedPanel' });
- const { $store } = wrapper.vm;
+ describe('when the panel is not expanded', () => {
+ beforeEach(() => {
+ createShallowWrapper({ hasMetrics: true });
+ setupStoreWithData(store);
+ return wrapper.vm.$nextTick();
+ });
+
+ it('expanded panel is not visible', () => {
+ expect(findExpandedPanel().isVisible()).toBe(false);
+ });
+
+ it('can set a panel as expanded', () => {
+ const panel = wrapper.findAll(DashboardPanel).at(1);
+
+ jest.spyOn(store, 'dispatch');
+
+ panel.vm.$emit('expand');
+
+ const groupData = metricsDashboardViewModel.panelGroups[0];
+
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setExpandedPanel', {
+ group: groupData.group,
+ panel: expect.objectContaining({
+ id: groupData.panels[0].id,
+ }),
+ });
+ });
+ });
+
+ describe('when the panel is expanded', () => {
+ let group;
+ let panel;
+
+ const mockKeyup = key => window.dispatchEvent(new KeyboardEvent('keyup', { key }));
+
+ const MockPanel = {
+ template: `<div><slot name="topLeft"/></div>`,
+ };
+
+ beforeEach(() => {
+ createShallowWrapper({ hasMetrics: true }, { stubs: { DashboardPanel: MockPanel } });
+ setupStoreWithData(store);
+
+ const { panelGroups } = store.state.monitoringDashboard.dashboard;
+
+ group = panelGroups[0].group;
+ [panel] = panelGroups[0].panels;
+
+ store.commit(`monitoringDashboard/${types.SET_EXPANDED_PANEL}`, {
+ group,
+ panel,
+ });
+
+ jest.spyOn(store, 'dispatch');
+
+ return wrapper.vm.$nextTick();
+ });
- setupStoreWithDashboard($store);
- setMetricResult({ $store, result: [], panel: 2 });
+ it('displays a single panel and others are hidden', () => {
+ const panels = wrapper.findAll(MockPanel);
+ const visiblePanels = panels.filter(w => w.isVisible());
+
+ expect(findExpandedPanel().isVisible()).toBe(true);
+ // v-show for hiding panels is more performant than v-if
+ // check for panels to be hidden.
+ expect(panels.length).toBe(metricsDashboardPanelCount + 1);
+ expect(visiblePanels.length).toBe(1);
+ });
+
+ it('sets a link to the expanded panel', () => {
+ const searchQuery =
+ '?dashboard=config%2Fprometheus%2Fcommon_metrics.yml&group=System%20metrics%20(Kubernetes)&title=Memory%20Usage%20(Total)&y_label=Total%20Memory%20Used%20(GB)';
+
+ expect(findExpandedPanel().attributes('clipboard-text')).toEqual(
+ expect.stringContaining(searchQuery),
+ );
+ });
+
+ it('restores full dashboard by clicking `back`', () => {
+ wrapper.find({ ref: 'goBackBtn' }).vm.$emit('click');
+
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'monitoringDashboard/clearExpandedPanel',
+ undefined,
+ );
+ });
+
+ it('restores dashboard from full screen by typing the Escape key', () => {
+ mockKeyup(ESC_KEY);
+ expect(store.dispatch).toHaveBeenCalledWith(
+ `monitoringDashboard/clearExpandedPanel`,
+ undefined,
+ );
+ });
+
+ it('restores dashboard from full screen by typing the Escape key on IE11', () => {
+ mockKeyup(ESC_KEY_IE11);
+
+ expect(store.dispatch).toHaveBeenCalledWith(
+ `monitoringDashboard/clearExpandedPanel`,
+ undefined,
+ );
+ });
+ });
+ });
+
+ describe('when one of the metrics is missing', () => {
+ beforeEach(() => {
+ createShallowWrapper({ hasMetrics: true });
+
+ setupStoreWithDashboard(store);
+ setMetricResult({ store, result: [], panel: 2 });
return wrapper.vm.$nextTick();
});
@@ -249,19 +618,17 @@ describe('Dashboard', () => {
describe('searchable environments dropdown', () => {
beforeEach(() => {
- createMountedWrapper(
- { hasMetrics: true },
- {
- attachToDocument: true,
- stubs: ['graph-group', 'panel-type'],
- },
- );
+ createMountedWrapper({ hasMetrics: true }, { attachToDocument: true });
- setupStoreWithData(wrapper.vm.$store);
+ setupStoreWithData(store);
return wrapper.vm.$nextTick();
});
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
it('renders a search input', () => {
expect(wrapper.find({ ref: 'monitorEnvironmentsDropdownSearch' }).exists()).toBe(true);
});
@@ -304,7 +671,7 @@ describe('Dashboard', () => {
});
it('shows loading element when environments fetch is still loading', () => {
- wrapper.vm.$store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`);
+ store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`);
return wrapper.vm
.$nextTick()
@@ -312,7 +679,7 @@ describe('Dashboard', () => {
expect(wrapper.find({ ref: 'monitorEnvironmentsDropdownLoading' }).exists()).toBe(true);
})
.then(() => {
- wrapper.vm.$store.commit(
+ store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
environmentData,
);
@@ -330,9 +697,11 @@ describe('Dashboard', () => {
const findRearrangeButton = () => wrapper.find('.js-rearrange-button');
beforeEach(() => {
- createShallowWrapper({ hasMetrics: true });
+ // call original dispatch
+ store.dispatch.mockRestore();
- setupStoreWithData(wrapper.vm.$store);
+ createShallowWrapper({ hasMetrics: true });
+ setupStoreWithData(store);
return wrapper.vm.$nextTick();
});
@@ -420,7 +789,7 @@ describe('Dashboard', () => {
createShallowWrapper({ hasMetrics: true, showHeader: false });
// all_dashboards is not defined in health dashboards
- wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined);
+ store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined);
return wrapper.vm.$nextTick();
});
@@ -440,10 +809,7 @@ describe('Dashboard', () => {
beforeEach(() => {
createShallowWrapper({ hasMetrics: true });
- wrapper.vm.$store.commit(
- `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
- dashboardGitResponse,
- );
+ setupAllDashboards(store);
return wrapper.vm.$nextTick();
});
@@ -452,10 +818,11 @@ describe('Dashboard', () => {
});
it('is present for a custom dashboard, and links to its edit_path', () => {
- const dashboard = dashboardGitResponse[1]; // non-default dashboard
- const currentDashboard = dashboard.path;
+ const dashboard = dashboardGitResponse[1];
+ store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
+ currentDashboard: dashboard.path,
+ });
- wrapper.setProps({ currentDashboard });
return wrapper.vm.$nextTick().then(() => {
expect(findEditLink().exists()).toBe(true);
expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path);
@@ -465,13 +832,8 @@ describe('Dashboard', () => {
describe('Dashboard dropdown', () => {
beforeEach(() => {
- createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
-
- wrapper.vm.$store.commit(
- `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
- dashboardGitResponse,
- );
-
+ createMountedWrapper({ hasMetrics: true });
+ setupAllDashboards(store);
return wrapper.vm.$nextTick();
});
@@ -484,15 +846,12 @@ describe('Dashboard', () => {
describe('external dashboard link', () => {
beforeEach(() => {
- createMountedWrapper(
- {
- hasMetrics: true,
- showPanels: false,
- showTimeWindowDropdown: false,
- externalDashboardUrl: '/mockUrl',
- },
- { stubs: ['graph-group', 'panel-type'] },
- );
+ createMountedWrapper({
+ hasMetrics: true,
+ showPanels: false,
+ showTimeWindowDropdown: false,
+ externalDashboardUrl: '/mockUrl',
+ });
return wrapper.vm.$nextTick();
});
@@ -507,45 +866,29 @@ describe('Dashboard', () => {
});
describe('Clipboard text in panels', () => {
- const currentDashboard = 'TEST_DASHBOARD';
+ const currentDashboard = dashboardGitResponse[1].path;
+ const panelIndex = 1; // skip expanded panel
- const getClipboardTextAt = i =>
+ const getClipboardTextFirstPanel = () =>
wrapper
- .findAll(PanelType)
- .at(i)
+ .findAll(DashboardPanel)
+ .at(panelIndex)
.props('clipboardText');
beforeEach(() => {
+ setupStoreWithData(store);
createShallowWrapper({ hasMetrics: true, currentDashboard });
- setupStoreWithData(wrapper.vm.$store);
-
return wrapper.vm.$nextTick();
});
it('contains a link to the dashboard', () => {
- expect(getClipboardTextAt(0)).toContain(`dashboard=${currentDashboard}`);
- expect(getClipboardTextAt(0)).toContain(`group=`);
- expect(getClipboardTextAt(0)).toContain(`title=`);
- expect(getClipboardTextAt(0)).toContain(`y_label=`);
- });
-
- it('strips the undefined parameter', () => {
- wrapper.setProps({ currentDashboard: undefined });
-
- return wrapper.vm.$nextTick(() => {
- expect(getClipboardTextAt(0)).not.toContain(`dashboard=`);
- expect(getClipboardTextAt(0)).toContain(`y_label=`);
- });
- });
+ const dashboardParam = `dashboard=${encodeURIComponent(currentDashboard)}`;
- it('null parameter is stripped', () => {
- wrapper.setProps({ currentDashboard: null });
-
- return wrapper.vm.$nextTick(() => {
- expect(getClipboardTextAt(0)).not.toContain(`dashboard=`);
- expect(getClipboardTextAt(0)).toContain(`y_label=`);
- });
+ expect(getClipboardTextFirstPanel()).toContain(dashboardParam);
+ expect(getClipboardTextFirstPanel()).toContain(`group=`);
+ expect(getClipboardTextFirstPanel()).toContain(`title=`);
+ expect(getClipboardTextFirstPanel()).toContain(`y_label=`);
});
});
@@ -572,7 +915,7 @@ describe('Dashboard', () => {
customMetricsPath: '/endpoint',
customMetricsAvailable: true,
});
- setupStoreWithData(wrapper.vm.$store);
+ setupStoreWithData(store);
origPage = document.body.dataset.page;
document.body.dataset.page = 'projects:environments:metrics';
diff --git a/spec/frontend/monitoring/components/dashboard_template_spec.js b/spec/frontend/monitoring/components/dashboard_template_spec.js
index d1790df4189..cc0ac348b11 100644
--- a/spec/frontend/monitoring/components/dashboard_template_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_template_spec.js
@@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { createStore } from '~/monitoring/stores';
+import { setupAllDashboards } from '../store_utils';
import { propsData } from '../mock_data';
jest.mock('~/lib/utils/url_utility');
@@ -15,24 +16,16 @@ describe('Dashboard template', () => {
beforeEach(() => {
store = createStore();
mock = new MockAdapter(axios);
+
+ setupAllDashboards(store);
});
afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- wrapper = null;
- }
mock.restore();
});
it('matches the default snapshot', () => {
- wrapper = shallowMount(Dashboard, {
- propsData: { ...propsData },
- methods: {
- fetchData: jest.fn(),
- },
- store,
- });
+ wrapper = shallowMount(Dashboard, { propsData: { ...propsData }, store });
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/monitoring/components/dashboard_url_time_spec.js b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
index 65e9d036d1a..9bba5280007 100644
--- a/spec/frontend/monitoring/components/dashboard_url_time_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
@@ -27,7 +27,7 @@ describe('dashboard invalid url parameters', () => {
wrapper = mount(Dashboard, {
propsData: { ...propsData, ...props },
store,
- stubs: ['graph-group', 'panel-type'],
+ stubs: ['graph-group', 'dashboard-panel'],
...options,
});
};
diff --git a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
index 0bcfabe6415..b29d86cbc5b 100644
--- a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
+++ b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { GlDropdownItem, GlModal, GlLoadingIcon, GlAlert } from '@gitlab/ui';
+import { GlDropdownItem, GlModal, GlLoadingIcon, GlAlert, GlIcon } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
@@ -9,36 +9,48 @@ import { dashboardGitResponse } from '../mock_data';
const defaultBranch = 'master';
-function createComponent(props, opts = {}) {
- const storeOpts = {
- methods: {
- duplicateSystemDashboard: jest.fn(),
- },
- computed: {
- allDashboards: () => dashboardGitResponse,
- },
- };
-
- return shallowMount(DashboardsDropdown, {
- propsData: {
- ...props,
- defaultBranch,
- },
- sync: false,
- ...storeOpts,
- ...opts,
- });
-}
+const starredDashboards = dashboardGitResponse.filter(({ starred }) => starred);
+const notStarredDashboards = dashboardGitResponse.filter(({ starred }) => !starred);
describe('DashboardsDropdown', () => {
let wrapper;
+ let mockDashboards;
+ let mockSelectedDashboard;
+
+ function createComponent(props, opts = {}) {
+ const storeOpts = {
+ methods: {
+ duplicateSystemDashboard: jest.fn(),
+ },
+ computed: {
+ allDashboards: () => mockDashboards,
+ selectedDashboard: () => mockSelectedDashboard,
+ },
+ };
+
+ return shallowMount(DashboardsDropdown, {
+ propsData: {
+ ...props,
+ defaultBranch,
+ },
+ sync: false,
+ ...storeOpts,
+ ...opts,
+ });
+ }
const findItems = () => wrapper.findAll(GlDropdownItem);
const findItemAt = i => wrapper.findAll(GlDropdownItem).at(i);
const findSearchInput = () => wrapper.find({ ref: 'monitorDashboardsDropdownSearch' });
const findNoItemsMsg = () => wrapper.find({ ref: 'monitorDashboardsDropdownMsg' });
+ const findStarredListDivider = () => wrapper.find({ ref: 'starredListDivider' });
const setSearchTerm = searchTerm => wrapper.setData({ searchTerm });
+ beforeEach(() => {
+ mockDashboards = dashboardGitResponse;
+ mockSelectedDashboard = null;
+ });
+
describe('when it receives dashboards data', () => {
beforeEach(() => {
wrapper = createComponent();
@@ -48,10 +60,14 @@ describe('DashboardsDropdown', () => {
expect(findItems().length).toEqual(dashboardGitResponse.length);
});
- it('displays items with the dashboard display name', () => {
- expect(findItemAt(0).text()).toBe(dashboardGitResponse[0].display_name);
- expect(findItemAt(1).text()).toBe(dashboardGitResponse[1].display_name);
- expect(findItemAt(2).text()).toBe(dashboardGitResponse[2].display_name);
+ it('displays items with the dashboard display name, with starred dashboards first', () => {
+ expect(findItemAt(0).text()).toBe(starredDashboards[0].display_name);
+ expect(findItemAt(1).text()).toBe(notStarredDashboards[0].display_name);
+ expect(findItemAt(2).text()).toBe(notStarredDashboards[1].display_name);
+ });
+
+ it('displays separator between starred and not starred dashboards', () => {
+ expect(findStarredListDivider().exists()).toBe(true);
});
it('displays a search input', () => {
@@ -81,18 +97,71 @@ describe('DashboardsDropdown', () => {
});
});
+ describe('when the dashboard is missing a display name', () => {
+ beforeEach(() => {
+ mockDashboards = dashboardGitResponse.map(d => ({ ...d, display_name: undefined }));
+ wrapper = createComponent();
+ });
+
+ it('displays items with the dashboard path, with starred dashboards first', () => {
+ expect(findItemAt(0).text()).toBe(starredDashboards[0].path);
+ expect(findItemAt(1).text()).toBe(notStarredDashboards[0].path);
+ expect(findItemAt(2).text()).toBe(notStarredDashboards[1].path);
+ });
+ });
+
+ describe('when it receives starred dashboards', () => {
+ beforeEach(() => {
+ mockDashboards = starredDashboards;
+ wrapper = createComponent();
+ });
+
+ it('displays an item for each dashboard', () => {
+ expect(findItems().length).toEqual(starredDashboards.length);
+ });
+
+ it('displays a star icon', () => {
+ const star = findItemAt(0).find(GlIcon);
+ expect(star.exists()).toBe(true);
+ expect(star.attributes('name')).toBe('star');
+ });
+
+ it('displays no separator between starred and not starred dashboards', () => {
+ expect(findStarredListDivider().exists()).toBe(false);
+ });
+ });
+
+ describe('when it receives only not-starred dashboards', () => {
+ beforeEach(() => {
+ mockDashboards = notStarredDashboards;
+ wrapper = createComponent();
+ });
+
+ it('displays an item for each dashboard', () => {
+ expect(findItems().length).toEqual(notStarredDashboards.length);
+ });
+
+ it('displays no star icon', () => {
+ const star = findItemAt(0).find(GlIcon);
+ expect(star.exists()).toBe(false);
+ });
+
+ it('displays no separator between starred and not starred dashboards', () => {
+ expect(findStarredListDivider().exists()).toBe(false);
+ });
+ });
+
describe('when a system dashboard is selected', () => {
let duplicateDashboardAction;
let modalDirective;
beforeEach(() => {
+ [mockSelectedDashboard] = dashboardGitResponse;
modalDirective = jest.fn();
duplicateDashboardAction = jest.fn().mockResolvedValue();
wrapper = createComponent(
- {
- selectedDashboard: dashboardGitResponse[0],
- },
+ {},
{
directives: {
GlModal: modalDirective,
@@ -260,7 +329,7 @@ describe('DashboardsDropdown', () => {
expect(wrapper.emitted().selectDashboard).toBeTruthy();
});
it('emits a "selectDashboard" event with dashboard information', () => {
- expect(wrapper.emitted().selectDashboard[0]).toEqual([dashboardGitResponse[1]]);
+ expect(wrapper.emitted().selectDashboard[0]).toEqual([dashboardGitResponse[0]]);
});
});
});
diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js
index 10fd58f749d..216ec345552 100644
--- a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js
+++ b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js
@@ -81,7 +81,8 @@ describe('DuplicateDashboardForm', () => {
it('with the inital form values', () => {
expect(wrapper.emitted().change).toHaveLength(1);
- expect(lastChange()).resolves.toEqual({
+
+ return expect(lastChange()).resolves.toEqual({
branch: '',
commitMessage: expect.any(String),
dashboard: dashboardGitResponse[0].path,
@@ -92,7 +93,7 @@ describe('DuplicateDashboardForm', () => {
it('containing an inputted file name', () => {
setValue('fileName', 'my_dashboard.yml');
- expect(lastChange()).resolves.toMatchObject({
+ return expect(lastChange()).resolves.toMatchObject({
fileName: 'my_dashboard.yml',
});
});
@@ -100,7 +101,7 @@ describe('DuplicateDashboardForm', () => {
it('containing a default commit message when no message is set', () => {
setValue('commitMessage', '');
- expect(lastChange()).resolves.toMatchObject({
+ return expect(lastChange()).resolves.toMatchObject({
commitMessage: expect.stringContaining('Create custom dashboard'),
});
});
@@ -108,7 +109,7 @@ describe('DuplicateDashboardForm', () => {
it('containing an inputted commit message', () => {
setValue('commitMessage', 'My commit message');
- expect(lastChange()).resolves.toMatchObject({
+ return expect(lastChange()).resolves.toMatchObject({
commitMessage: expect.stringContaining('My commit message'),
});
});
@@ -116,7 +117,7 @@ describe('DuplicateDashboardForm', () => {
it('containing an inputted branch name', () => {
setValue('branchName', 'a-new-branch');
- expect(lastChange()).resolves.toMatchObject({
+ return expect(lastChange()).resolves.toMatchObject({
branch: 'a-new-branch',
});
});
@@ -125,13 +126,14 @@ describe('DuplicateDashboardForm', () => {
setChecked(wrapper.vm.$options.radioVals.DEFAULT);
setValue('branchName', 'a-new-branch');
- expect(lastChange()).resolves.toMatchObject({
- branch: defaultBranch,
- });
-
- return wrapper.vm.$nextTick(() => {
- expect(findByRef('branchName').isVisible()).toBe(false);
- });
+ return Promise.all([
+ expect(lastChange()).resolves.toMatchObject({
+ branch: defaultBranch,
+ }),
+ wrapper.vm.$nextTick(() => {
+ expect(findByRef('branchName').isVisible()).toBe(false);
+ }),
+ ]);
});
it('when `new` branch option is chosen, focuses on the branch name input', () => {
diff --git a/spec/frontend/monitoring/components/embeds/metric_embed_spec.js b/spec/frontend/monitoring/components/embeds/metric_embed_spec.js
index b829cd53479..f23823ccad6 100644
--- a/spec/frontend/monitoring/components/embeds/metric_embed_spec.js
+++ b/spec/frontend/monitoring/components/embeds/metric_embed_spec.js
@@ -1,6 +1,6 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
-import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
+import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import { TEST_HOST } from 'helpers/test_constants';
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
import { groups, initialState, metricsData, metricsWithData } from './mock_data';
@@ -62,7 +62,7 @@ describe('MetricEmbed', () => {
it('shows an empty state when no metrics are present', () => {
expect(wrapper.find('.metrics-embed').exists()).toBe(true);
- expect(wrapper.find(PanelType).exists()).toBe(false);
+ expect(wrapper.find(DashboardPanel).exists()).toBe(false);
});
});
@@ -90,12 +90,12 @@ describe('MetricEmbed', () => {
it('shows a chart when metrics are present', () => {
expect(wrapper.find('.metrics-embed').exists()).toBe(true);
- expect(wrapper.find(PanelType).exists()).toBe(true);
- expect(wrapper.findAll(PanelType).length).toBe(2);
+ expect(wrapper.find(DashboardPanel).exists()).toBe(true);
+ expect(wrapper.findAll(DashboardPanel).length).toBe(2);
});
it('includes groupId with dashboardUrl', () => {
- expect(wrapper.find(PanelType).props('groupId')).toBe(TEST_HOST);
+ expect(wrapper.find(DashboardPanel).props('groupId')).toBe(TEST_HOST);
});
});
});
diff --git a/spec/frontend/monitoring/components/variables/custom_variable_spec.js b/spec/frontend/monitoring/components/variables/custom_variable_spec.js
new file mode 100644
index 00000000000..5a2b26219b6
--- /dev/null
+++ b/spec/frontend/monitoring/components/variables/custom_variable_spec.js
@@ -0,0 +1,52 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import CustomVariable from '~/monitoring/components/variables/custom_variable.vue';
+
+describe('Custom variable component', () => {
+ let wrapper;
+ const propsData = {
+ name: 'env',
+ label: 'Select environment',
+ value: 'Production',
+ options: [{ text: 'Production', value: 'prod' }, { text: 'Canary', value: 'canary' }],
+ };
+ const createShallowWrapper = () => {
+ wrapper = shallowMount(CustomVariable, {
+ propsData,
+ });
+ };
+
+ const findDropdown = () => wrapper.find(GlDropdown);
+ const findDropdownItems = () => wrapper.findAll(GlDropdownItem);
+
+ it('renders dropdown element when all necessary props are passed', () => {
+ createShallowWrapper();
+
+ expect(findDropdown()).toExist();
+ });
+
+ it('renders dropdown element with a text', () => {
+ createShallowWrapper();
+
+ expect(findDropdown().attributes('text')).toBe(propsData.value);
+ });
+
+ it('renders all the dropdown items', () => {
+ createShallowWrapper();
+
+ expect(findDropdownItems()).toHaveLength(propsData.options.length);
+ });
+
+ it('changing dropdown items triggers update', () => {
+ createShallowWrapper();
+ jest.spyOn(wrapper.vm, '$emit');
+
+ findDropdownItems()
+ .at(1)
+ .vm.$emit('click');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'env', 'canary');
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/variables/text_variable_spec.js b/spec/frontend/monitoring/components/variables/text_variable_spec.js
new file mode 100644
index 00000000000..f01584ae8bc
--- /dev/null
+++ b/spec/frontend/monitoring/components/variables/text_variable_spec.js
@@ -0,0 +1,59 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlFormInput } from '@gitlab/ui';
+import TextVariable from '~/monitoring/components/variables/text_variable.vue';
+
+describe('Text variable component', () => {
+ let wrapper;
+ const propsData = {
+ name: 'pod',
+ label: 'Select pod',
+ value: 'test-pod',
+ };
+ const createShallowWrapper = () => {
+ wrapper = shallowMount(TextVariable, {
+ propsData,
+ });
+ };
+
+ const findInput = () => wrapper.find(GlFormInput);
+
+ it('renders a text input when all props are passed', () => {
+ createShallowWrapper();
+
+ expect(findInput()).toExist();
+ });
+
+ it('always has a default value', () => {
+ createShallowWrapper();
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findInput().attributes('value')).toBe(propsData.value);
+ });
+ });
+
+ it('triggers keyup enter', () => {
+ createShallowWrapper();
+ jest.spyOn(wrapper.vm, '$emit');
+
+ findInput().element.value = 'prod-pod';
+ findInput().trigger('input');
+ findInput().trigger('keyup.enter');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'pod', 'prod-pod');
+ });
+ });
+
+ it('triggers blur enter', () => {
+ createShallowWrapper();
+ jest.spyOn(wrapper.vm, '$emit');
+
+ findInput().element.value = 'canary-pod';
+ findInput().trigger('input');
+ findInput().trigger('blur');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'pod', 'canary-pod');
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/variables_section_spec.js b/spec/frontend/monitoring/components/variables_section_spec.js
new file mode 100644
index 00000000000..095d89c9231
--- /dev/null
+++ b/spec/frontend/monitoring/components/variables_section_spec.js
@@ -0,0 +1,126 @@
+import { shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import VariablesSection from '~/monitoring/components/variables_section.vue';
+import CustomVariable from '~/monitoring/components/variables/custom_variable.vue';
+import TextVariable from '~/monitoring/components/variables/text_variable.vue';
+import { updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
+import { createStore } from '~/monitoring/stores';
+import { convertVariablesForURL } from '~/monitoring/utils';
+import * as types from '~/monitoring/stores/mutation_types';
+import { mockTemplatingDataResponses } from '../mock_data';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ updateHistory: jest.fn(),
+ mergeUrlParams: jest.fn(),
+}));
+
+describe('Metrics dashboard/variables section component', () => {
+ let store;
+ let wrapper;
+ const sampleVariables = {
+ label1: mockTemplatingDataResponses.simpleText.simpleText,
+ label2: mockTemplatingDataResponses.advText.advText,
+ label3: mockTemplatingDataResponses.simpleCustom.simpleCustom,
+ };
+
+ const createShallowWrapper = () => {
+ wrapper = shallowMount(VariablesSection, {
+ store,
+ });
+ };
+
+ const findTextInput = () => wrapper.findAll(TextVariable);
+ const findCustomInput = () => wrapper.findAll(CustomVariable);
+
+ beforeEach(() => {
+ store = createStore();
+
+ store.state.monitoringDashboard.showEmptyState = false;
+ });
+
+ it('does not show the variables section', () => {
+ createShallowWrapper();
+ const allInputs = findTextInput().length + findCustomInput().length;
+
+ expect(allInputs).toBe(0);
+ });
+
+ it('shows the variables section', () => {
+ createShallowWrapper();
+ store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, sampleVariables);
+
+ return wrapper.vm.$nextTick(() => {
+ const allInputs = findTextInput().length + findCustomInput().length;
+
+ expect(allInputs).toBe(Object.keys(sampleVariables).length);
+ });
+ });
+
+ describe('when changing the variable inputs', () => {
+ const fetchDashboardData = jest.fn();
+ const updateVariableValues = jest.fn();
+
+ beforeEach(() => {
+ store = new Vuex.Store({
+ modules: {
+ monitoringDashboard: {
+ namespaced: true,
+ state: {
+ showEmptyState: false,
+ promVariables: sampleVariables,
+ },
+ actions: {
+ fetchDashboardData,
+ updateVariableValues,
+ },
+ },
+ },
+ });
+
+ createShallowWrapper();
+ });
+
+ it('merges the url params and refreshes the dashboard when a text-based variables inputs are updated', () => {
+ const firstInput = findTextInput().at(0);
+
+ firstInput.vm.$emit('onUpdate', 'label1', 'test');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(updateVariableValues).toHaveBeenCalled();
+ expect(mergeUrlParams).toHaveBeenCalledWith(
+ convertVariablesForURL(sampleVariables),
+ window.location.href,
+ );
+ expect(updateHistory).toHaveBeenCalled();
+ expect(fetchDashboardData).toHaveBeenCalled();
+ });
+ });
+
+ it('merges the url params and refreshes the dashboard when a custom-based variables inputs are updated', () => {
+ const firstInput = findCustomInput().at(0);
+
+ firstInput.vm.$emit('onUpdate', 'label1', 'test');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(updateVariableValues).toHaveBeenCalled();
+ expect(mergeUrlParams).toHaveBeenCalledWith(
+ convertVariablesForURL(sampleVariables),
+ window.location.href,
+ );
+ expect(updateHistory).toHaveBeenCalled();
+ expect(fetchDashboardData).toHaveBeenCalled();
+ });
+ });
+
+ it('does not merge the url params and refreshes the dashboard if the value entered is not different that is what currently stored', () => {
+ const firstInput = findTextInput().at(0);
+
+ firstInput.vm.$emit('onUpdate', 'label1', 'Simple text');
+
+ expect(updateVariableValues).not.toHaveBeenCalled();
+ expect(mergeUrlParams).not.toHaveBeenCalled();
+ expect(updateHistory).not.toHaveBeenCalled();
+ expect(fetchDashboardData).not.toHaveBeenCalled();
+ });
+ });
+});