diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-31 09:08:16 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-31 09:08:16 +0000 |
commit | 6044caed20964a70c1ac6c5a3365d567ed96dfde (patch) | |
tree | 3fe8f14b4acbd542265544843efeb6f59b5d3efe /spec/frontend/monitoring/components | |
parent | 92077e0f8d70c70a908395808b16f98ecd3a5fcd (diff) | |
download | gitlab-ce-6044caed20964a70c1ac6c5a3365d567ed96dfde.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/monitoring/components')
4 files changed, 414 insertions, 10 deletions
diff --git a/spec/frontend/monitoring/components/embeds/embed_group_spec.js b/spec/frontend/monitoring/components/embeds/embed_group_spec.js new file mode 100644 index 00000000000..54d21def603 --- /dev/null +++ b/spec/frontend/monitoring/components/embeds/embed_group_spec.js @@ -0,0 +1,163 @@ +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'); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/embeds/metric_embed_spec.js b/spec/frontend/monitoring/components/embeds/metric_embed_spec.js new file mode 100644 index 00000000000..d0fe22cefec --- /dev/null +++ b/spec/frontend/monitoring/components/embeds/metric_embed_spec.js @@ -0,0 +1,102 @@ +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 MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue'; +import { groups, initialState, metricsData, metricsWithData } from './mock_data'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('MetricEmbed', () => { + let wrapper; + let store; + let actions; + let metricsWithDataGetter; + + function mountComponent() { + wrapper = shallowMount(MetricEmbed, { + localVue, + store, + propsData: { + dashboardUrl: TEST_HOST, + }, + }); + } + + beforeEach(() => { + actions = { + setFeatureFlags: jest.fn(), + setShowErrorBanner: jest.fn(), + setEndpoints: jest.fn(), + setTimeRange: jest.fn(), + fetchDashboard: jest.fn(), + }; + + metricsWithDataGetter = jest.fn(); + + store = new Vuex.Store({ + modules: { + monitoringDashboard: { + namespaced: true, + actions, + getters: { + metricsWithData: () => metricsWithDataGetter, + }, + state: initialState, + }, + }, + }); + }); + + afterEach(() => { + metricsWithDataGetter.mockClear(); + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('no metrics are available yet', () => { + beforeEach(() => { + mountComponent(); + }); + + 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); + }); + }); + + describe('metrics are available', () => { + beforeEach(() => { + store.state.monitoringDashboard.dashboard.panelGroups = groups; + store.state.monitoringDashboard.dashboard.panelGroups[0].panels = metricsData; + + metricsWithDataGetter.mockReturnValue(metricsWithData); + + mountComponent(); + }); + + it('calls actions to fetch data', () => { + const expectedTimeRangePayload = expect.objectContaining({ + start: expect.any(String), + end: expect.any(String), + }); + + expect(actions.setTimeRange).toHaveBeenCalledTimes(1); + expect(actions.setTimeRange.mock.calls[0][1]).toEqual(expectedTimeRangePayload); + + expect(actions.fetchDashboard).toHaveBeenCalled(); + }); + + 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); + }); + + it('includes groupId with dashboardUrl', () => { + expect(wrapper.find(PanelType).props('groupId')).toBe(TEST_HOST); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/embeds/mock_data.js b/spec/frontend/monitoring/components/embeds/mock_data.js new file mode 100644 index 00000000000..9cf66e52d22 --- /dev/null +++ b/spec/frontend/monitoring/components/embeds/mock_data.js @@ -0,0 +1,87 @@ +import { TEST_HOST } from 'helpers/test_constants'; + +export const metricsWithData = ['15_metric_a', '16_metric_b']; + +export const groups = [ + { + panels: [ + { + title: 'Memory Usage (Total)', + type: 'area-chart', + y_label: 'Total Memory Used', + metrics: null, + }, + ], + }, +]; + +const result = [ + { + values: [ + ['Mon', 1220], + ['Tue', 932], + ['Wed', 901], + ['Thu', 934], + ['Fri', 1290], + ['Sat', 1330], + ['Sun', 1320], + ], + }, +]; + +export const metricsData = [ + { + metrics: [ + { + metricId: '15_metric_a', + result, + }, + ], + }, + { + metrics: [ + { + metricId: '16_metric_b', + result, + }, + ], + }, +]; + +export const initialState = () => ({ + dashboard: { + panel_groups: [], + }, + 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'; diff --git a/spec/frontend/monitoring/components/panel_type_spec.js b/spec/frontend/monitoring/components/panel_type_spec.js index 927d93ab697..782a276a91b 100644 --- a/spec/frontend/monitoring/components/panel_type_spec.js +++ b/spec/frontend/monitoring/components/panel_type_spec.js @@ -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); + }); + }); }); |