summaryrefslogtreecommitdiff
path: root/spec/frontend/pipelines/graph_shared/links_layer_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/pipelines/graph_shared/links_layer_spec.js')
-rw-r--r--spec/frontend/pipelines/graph_shared/links_layer_spec.js200
1 files changed, 156 insertions, 44 deletions
diff --git a/spec/frontend/pipelines/graph_shared/links_layer_spec.js b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
index 5e5365eef30..932a19f2f00 100644
--- a/spec/frontend/pipelines/graph_shared/links_layer_spec.js
+++ b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
@@ -1,32 +1,33 @@
-import { GlAlert } from '@gitlab/ui';
-import { fireEvent, within } from '@testing-library/dom';
-import { mount, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import {
+ PIPELINES_DETAIL_LINK_DURATION,
+ PIPELINES_DETAIL_LINKS_TOTAL,
+ PIPELINES_DETAIL_LINKS_JOB_RATIO,
+} from '~/performance/constants';
+import * as perfUtils from '~/performance/utils';
+import * as Api from '~/pipelines/components/graph_shared/api';
import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
+import * as sentryUtils from '~/pipelines/utils';
import { generateResponse, mockPipelineResponse } from '../graph/mock_data';
describe('links layer component', () => {
let wrapper;
- const withinComponent = () => within(wrapper.element);
- const findAlert = () => wrapper.find(GlAlert);
- const findShowAnyways = () =>
- withinComponent().getByText(wrapper.vm.$options.i18n.showLinksAnyways);
const findLinksInner = () => wrapper.find(LinksInner);
const pipeline = generateResponse(mockPipelineResponse, 'root/fungi-xoxo');
const containerId = `pipeline-links-container-${pipeline.id}`;
const slotContent = "<div>Ceci n'est pas un graphique</div>";
- const tooManyStages = Array(101)
- .fill(0)
- .flatMap(() => pipeline.stages);
-
const defaultProps = {
containerId,
containerMeasurements: { width: 400, height: 400 },
pipelineId: pipeline.id,
pipelineData: pipeline.stages,
+ showLinks: false,
};
const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => {
@@ -46,10 +47,9 @@ describe('links layer component', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
- describe('with data under max stages', () => {
+ describe('with show links off', () => {
beforeEach(() => {
createComponent();
});
@@ -58,62 +58,174 @@ describe('links layer component', () => {
expect(wrapper.html()).toContain(slotContent);
});
+ it('does not render inner links component', () => {
+ expect(findLinksInner().exists()).toBe(false);
+ });
+ });
+
+ describe('with show links on', () => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ showLinks: true,
+ },
+ });
+ });
+
+ it('renders the default slot', () => {
+ expect(wrapper.html()).toContain(slotContent);
+ });
+
it('renders the inner links component', () => {
expect(findLinksInner().exists()).toBe(true);
});
});
- describe('with more than the max number of stages', () => {
- describe('rendering', () => {
- beforeEach(() => {
- createComponent({ props: { pipelineData: tooManyStages } });
- });
+ describe('with width or height measurement at 0', () => {
+ beforeEach(() => {
+ createComponent({ props: { containerMeasurements: { width: 0, height: 100 } } });
+ });
- it('renders the default slot', () => {
- expect(wrapper.html()).toContain(slotContent);
- });
+ it('renders the default slot', () => {
+ expect(wrapper.html()).toContain(slotContent);
+ });
- it('renders the alert component', () => {
- expect(findAlert().exists()).toBe(true);
- });
+ it('does not render the inner links component', () => {
+ expect(findLinksInner().exists()).toBe(false);
+ });
+ });
- it('does not render the inner links component', () => {
- expect(findLinksInner().exists()).toBe(false);
- });
+ describe('performance metrics', () => {
+ const metricsPath = '/root/project/-/ci/prometheus_metrics/histograms.json';
+ let markAndMeasure;
+ let reportToSentry;
+ let reportPerformance;
+ let mock;
+
+ beforeEach(() => {
+ jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => cb());
+ markAndMeasure = jest.spyOn(perfUtils, 'performanceMarkAndMeasure');
+ reportToSentry = jest.spyOn(sentryUtils, 'reportToSentry');
+ reportPerformance = jest.spyOn(Api, 'reportPerformance');
});
- describe('with width or height measurement at 0', () => {
+ describe('with no metrics config object', () => {
beforeEach(() => {
- createComponent({ props: { containerMeasurements: { width: 0, height: 100 } } });
+ createComponent();
});
- it('renders the default slot', () => {
- expect(wrapper.html()).toContain(slotContent);
+ it('is not called', () => {
+ expect(markAndMeasure).not.toHaveBeenCalled();
+ expect(reportToSentry).not.toHaveBeenCalled();
+ expect(reportPerformance).not.toHaveBeenCalled();
});
+ });
- it('does not render the alert component', () => {
- expect(findAlert().exists()).toBe(false);
+ describe('with metrics config set to false', () => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ metricsConfig: {
+ collectMetrics: false,
+ metricsPath: '/path/to/metrics',
+ },
+ },
+ });
});
- it('does not render the inner links component', () => {
- expect(findLinksInner().exists()).toBe(false);
+ it('is not called', () => {
+ expect(markAndMeasure).not.toHaveBeenCalled();
+ expect(reportToSentry).not.toHaveBeenCalled();
+ expect(reportPerformance).not.toHaveBeenCalled();
});
});
- describe('interactions', () => {
+ describe('with no metrics path', () => {
beforeEach(() => {
- createComponent({ mountFn: mount, props: { pipelineData: tooManyStages } });
+ createComponent({
+ props: {
+ metricsConfig: {
+ collectMetrics: true,
+ metricsPath: '',
+ },
+ },
+ });
});
- it('renders the disable button', () => {
- expect(findShowAnyways()).not.toBe(null);
+ it('is not called', () => {
+ expect(markAndMeasure).not.toHaveBeenCalled();
+ expect(reportToSentry).not.toHaveBeenCalled();
+ expect(reportPerformance).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('with metrics path and collect set to true', () => {
+ const duration = 875;
+ const numLinks = 7;
+ const totalGroups = 8;
+ const metricsData = {
+ histograms: [
+ { name: PIPELINES_DETAIL_LINK_DURATION, value: duration / 1000 },
+ { name: PIPELINES_DETAIL_LINKS_TOTAL, value: numLinks },
+ {
+ name: PIPELINES_DETAIL_LINKS_JOB_RATIO,
+ value: numLinks / totalGroups,
+ },
+ ],
+ };
+
+ describe('when no duration is obtained', () => {
+ beforeEach(() => {
+ jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => {
+ return [];
+ });
+
+ createComponent({
+ props: {
+ metricsConfig: {
+ collectMetrics: true,
+ path: metricsPath,
+ },
+ },
+ });
+ });
+
+ it('attempts to collect metrics', () => {
+ expect(markAndMeasure).toHaveBeenCalled();
+ expect(reportPerformance).not.toHaveBeenCalled();
+ expect(reportToSentry).not.toHaveBeenCalled();
+ });
});
- it('shows links when override is clicked', async () => {
- expect(findLinksInner().exists()).toBe(false);
- fireEvent(findShowAnyways(), new MouseEvent('click', { bubbles: true }));
- await wrapper.vm.$nextTick();
- expect(findLinksInner().exists()).toBe(true);
+ describe('with duration and no error', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ mock.onPost(metricsPath).reply(200, {});
+
+ jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => {
+ return [{ duration }];
+ });
+
+ createComponent({
+ props: {
+ metricsConfig: {
+ collectMetrics: true,
+ path: metricsPath,
+ },
+ },
+ });
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('it calls reportPerformance with expected arguments', () => {
+ expect(markAndMeasure).toHaveBeenCalled();
+ expect(reportPerformance).toHaveBeenCalled();
+ expect(reportPerformance).toHaveBeenCalledWith(metricsPath, metricsData);
+ expect(reportToSentry).not.toHaveBeenCalled();
+ });
});
});
});