Commit 40dd556c authored by Paul Slaughter's avatar Paul Slaughter

Merge branch 'refactor-alert-widget-spec' into 'master'

Migrate ee/spec/javascripts/monitoring/alert_widget_spec.js to Jest"

Closes #32461

See merge request gitlab-org/gitlab!17398
parents a9106d15 ef075e0d
......@@ -54,18 +54,11 @@ export default {
.map(this.formatAlertSummary)
.join(', ');
},
supportsComputedAlerts() {
return gon.features && gon.features.prometheusComputedAlerts;
},
},
created() {
this.service = new AlertsService({ alertsEndpoint: this.alertsEndpoint });
this.fetchAlertData();
},
beforeDestroy() {
// clean up external event listeners
document.removeEventListener('click', this.handleOutsideClick);
},
methods: {
fetchAlertData() {
this.isLoading = true;
......
import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import createFlash from '~/flash';
import AlertWidget from 'ee/monitoring/components/alert_widget.vue';
import waitForPromises from 'helpers/wait_for_promises';
const mockReadAlert = jest.fn();
const mockCreateAlert = jest.fn();
const mockUpdateAlert = jest.fn();
const mockDeleteAlert = jest.fn();
jest.mock('~/flash');
jest.mock(
'ee/monitoring/services/alerts_service',
() =>
function AlertsServiceMock() {
return {
readAlert: mockReadAlert,
createAlert: mockCreateAlert,
updateAlert: mockUpdateAlert,
deleteAlert: mockDeleteAlert,
};
},
);
describe('AlertWidget', () => {
let wrapper;
const metricId = '5';
const alertPath = 'my/alert.json';
const relevantQueries = [{ metricId, label: 'alert-label', alert_path: alertPath }];
const defaultProps = {
alertsEndpoint: '',
relevantQueries,
alertsToManage: {},
modalId: 'alert-modal-1',
};
const propsWithAlert = {
relevantQueries,
};
const propsWithAlertData = {
relevantQueries,
alertsToManage: {
[alertPath]: { operator: '>', threshold: 42, alert_path: alertPath, metricId },
},
};
const createComponent = propsData => {
wrapper = shallowMount(AlertWidget, {
propsData: {
...defaultProps,
...propsData,
},
sync: false,
});
};
const findWidgetForm = () => wrapper.find({ ref: 'widgetForm' });
const findAlertErrorMessage = () => wrapper.find('.alert-error-message');
const findCurrentSettings = () => wrapper.find('.alert-current-setting');
afterEach(() => {
jest.clearAllMocks();
wrapper.destroy();
wrapper = null;
});
it('displays a loading spinner and disables form when fetching alerts', () => {
let resolveReadAlert;
mockReadAlert.mockReturnValue(
new Promise(resolve => {
resolveReadAlert = resolve;
}),
);
createComponent(defaultProps);
return wrapper.vm
.$nextTick()
.then(() => {
expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true);
expect(findWidgetForm().props('disabled')).toBe(true);
resolveReadAlert({ operator: '=', threshold: 42 });
})
.then(() => waitForPromises())
.then(() => {
expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(false);
expect(findWidgetForm().props('disabled')).toBe(false);
});
});
it('displays an error message when fetch fails', () => {
mockReadAlert.mockRejectedValue();
createComponent(propsWithAlert);
expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true);
return waitForPromises().then(() => {
expect(createFlash).toHaveBeenCalled();
expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(false);
});
});
it('displays an alert summary when there is a single alert', () => {
mockReadAlert.mockResolvedValue({ operator: '>', threshold: 42 });
createComponent(propsWithAlertData);
expect(wrapper.text()).toContain('alert-label > 42');
});
it('displays a combined alert summary when there are multiple alerts', () => {
mockReadAlert.mockResolvedValue({ operator: '>', threshold: 42 });
const propsWithManyAlerts = {
relevantQueries: relevantQueries.concat([
{ metricId: '6', alert_path: 'my/alert2.json', label: 'alert-label2' },
]),
alertsToManage: {
'my/alert.json': {
operator: '>',
threshold: 42,
alert_path: alertPath,
metricId,
},
'my/alert2.json': {
operator: '=',
threshold: 900,
alert_path: 'my/alert2.json',
metricId: '6',
},
},
};
createComponent(propsWithManyAlerts);
expect(findCurrentSettings().text()).toEqual('alert-label > 42, alert-label2 = 900');
});
it('creates an alert with an appropriate handler', () => {
const alertParams = {
operator: '<',
threshold: 4,
prometheus_metric_id: '5',
};
mockReadAlert.mockResolvedValue({ operator: '>', threshold: 42 });
const fakeAlertPath = 'foo/bar';
mockCreateAlert.mockResolvedValue({ alert_path: fakeAlertPath, ...alertParams });
createComponent({
alertsToManage: {
[fakeAlertPath]: {
alert_path: fakeAlertPath,
operator: '<',
threshold: 4,
prometheus_metric_id: '5',
metricId: '5',
},
},
});
findWidgetForm().vm.$emit('create', alertParams);
expect(mockCreateAlert).toHaveBeenCalledWith(alertParams);
});
it('updates an alert with an appropriate handler', () => {
const alertParams = { operator: '<', threshold: 4, alert_path: alertPath };
const newAlertParams = { operator: '=', threshold: 12 };
mockReadAlert.mockResolvedValue(alertParams);
mockUpdateAlert.mockResolvedValue({ ...alertParams, ...newAlertParams });
createComponent({
...propsWithAlertData,
alertsToManage: {
[alertPath]: {
alert_path: alertPath,
operator: '=',
threshold: 12,
metricId: '5',
},
},
});
findWidgetForm().vm.$emit('update', {
alert: alertPath,
...newAlertParams,
prometheus_metric_id: '5',
});
expect(mockUpdateAlert).toHaveBeenCalledWith(alertPath, newAlertParams);
});
it('deletes an alert with an appropriate handler', () => {
const alertParams = { alert_path: alertPath, operator: '>', threshold: 42 };
mockReadAlert.mockResolvedValue(alertParams);
mockDeleteAlert.mockResolvedValue({});
createComponent({
...propsWithAlert,
alertsToManage: {
[alertPath]: {
alert_path: alertPath,
operator: '>',
threshold: 42,
metricId: '5',
},
},
});
findWidgetForm().vm.$emit('delete', { alert: alertPath });
expect(mockDeleteAlert).toHaveBeenCalledWith(alertPath);
expect(findAlertErrorMessage().exists()).toBe(false);
});
describe('when delete fails', () => {
beforeEach(() => {
const alertParams = { alert_path: alertPath, operator: '>', threshold: 42 };
mockReadAlert.mockResolvedValue(alertParams);
mockDeleteAlert.mockRejectedValue();
createComponent({
...propsWithAlert,
alertsToManage: {
[alertPath]: {
alert_path: alertPath,
operator: '>',
threshold: 42,
metricId: '5',
},
},
});
findWidgetForm().vm.$emit('delete', { alert: alertPath });
});
it('shows error message', () => {
expect(findAlertErrorMessage().text()).toEqual('Error deleting alert');
});
it('dismisses error message on cancel', () => {
findWidgetForm().vm.$emit('cancel');
return wrapper.vm.$nextTick().then(() => {
expect(findAlertErrorMessage().exists()).toBe(false);
});
});
});
});
import Vue from 'vue';
import AlertWidget from 'ee/monitoring/components/alert_widget.vue';
import AlertsService from 'ee/monitoring/services/alerts_service';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import waitForPromises from 'spec/helpers/wait_for_promises';
describe('AlertWidget', () => {
let AlertWidgetComponent;
let vm;
const metricId = '5';
const alertPath = 'my/alert.json';
const relevantQueries = [{ metricId, label: 'alert-label', alert_path: alertPath }];
const props = {
alertsEndpoint: '',
relevantQueries,
alertsToManage: {},
modalId: 'alert-modal-1',
};
const propsWithAlert = {
...props,
relevantQueries,
};
const propsWithAlertData = {
...props,
relevantQueries,
alertsToManage: {
[alertPath]: { operator: '>', threshold: 42, alert_path: alertPath, metricId },
},
};
const mockSetAlerts = (path, data) => {
const alerts = data ? { [path]: data } : {};
Vue.set(vm, 'alertsToManage', alerts);
};
beforeAll(() => {
AlertWidgetComponent = Vue.extend(AlertWidget);
});
beforeEach(() => {
setFixtures('<div id="alert-widget"></div>');
});
afterEach(() => {
if (vm) vm.$destroy();
});
it('displays a loading spinner when fetching alerts', done => {
let resolveReadAlert;
spyOn(AlertsService.prototype, 'readAlert').and.returnValue(
new Promise(resolve => {
resolveReadAlert = resolve;
}),
);
vm = mountComponent(AlertWidgetComponent, propsWithAlert, '#alert-widget');
// expect loading spinner to exist during fetch
expect(vm.isLoading).toBeTruthy();
expect(vm.$refs.widgetForm.$props.disabled).toBe(true);
expect(vm.$el.querySelector('.loading-container')).toBeVisible();
resolveReadAlert({ operator: '=', threshold: 42 });
// expect loading spinner to go away after fetch
setTimeout(() =>
vm.$nextTick(() => {
expect(vm.isLoading).toEqual(false);
expect(vm.$el.querySelector('.loading-container')).toBeHidden();
expect(vm.$refs.widgetForm.$props.disabled).toBe(false);
done();
}),
);
});
it('displays an error message when fetch fails', done => {
const spy = spyOnDependency(AlertWidget, 'createFlash');
spyOn(AlertsService.prototype, 'readAlert').and.returnValue(Promise.reject());
vm = mountComponent(AlertWidgetComponent, propsWithAlert, '#alert-widget');
setTimeout(() =>
vm.$nextTick(() => {
expect(vm.isLoading).toEqual(false);
expect(spy).toHaveBeenCalled();
done();
}),
);
});
it('displays an alert summary when there is a single alert', () => {
spyOn(AlertsService.prototype, 'readAlert').and.returnValue(
Promise.resolve({ operator: '>', threshold: 42 }),
);
vm = mountComponent(AlertWidgetComponent, propsWithAlertData, '#alert-widget');
expect(vm.alertSummary).toBe('alert-label > 42');
expect(vm.$el.querySelector('.alert-current-setting')).toBeVisible();
});
it('displays a combined alert summary when there are multiple alerts', () => {
spyOn(AlertsService.prototype, 'readAlert').and.returnValue(
Promise.resolve({ operator: '>', threshold: 42 }),
);
const propsWithManyAlerts = {
...props,
relevantQueries: relevantQueries.concat([
{ metricId: '6', alert_path: 'my/alert2.json', label: 'alert-label2' },
]),
alertsToManage: {
'my/alert.json': {
operator: '>',
threshold: 42,
alert_path: alertPath,
metricId,
},
'my/alert2.json': {
operator: '=',
threshold: 900,
alert_path: 'my/alert2.json',
metricId: '6',
},
},
};
vm = mountComponent(AlertWidgetComponent, propsWithManyAlerts, '#alert-widget');
expect(vm.alertSummary).toBe('alert-label > 42, alert-label2 = 900');
expect(vm.$el.querySelector('.alert-current-setting')).toBeVisible();
});
it('creates an alert with an appropriate handler', done => {
const alertParams = {
operator: '<',
threshold: 4,
prometheus_metric_id: '5',
};
spyOn(AlertsService.prototype, 'createAlert').and.returnValue(
Promise.resolve({ alert_path: 'foo/bar', ...alertParams }),
);
vm = mountComponent(AlertWidgetComponent, props);
vm.$on('setAlerts', mockSetAlerts);
vm.$refs.widgetForm.$emit('create', alertParams);
expect(AlertsService.prototype.createAlert).toHaveBeenCalledWith(alertParams);
waitForPromises()
.then(() => {
expect(vm.isLoading).toEqual(false);
done();
})
.catch(done.fail);
});
it('dismisses error message when action is cancelled', () => {
vm = mountComponent(AlertWidgetComponent, props);
vm.$on('setAlerts', mockSetAlerts);
vm.errorMessage = 'Mock error message.';
// widget modal is dismissed
vm.$refs.widgetForm.$emit('cancel');
expect(vm.errorMessage).toBe(null);
});
it('updates an alert with an appropriate handler', done => {
const alertParams = { operator: '<', threshold: 4, alert_path: alertPath };
const newAlertParams = { operator: '=', threshold: 12 };
spyOn(AlertsService.prototype, 'readAlert').and.returnValue(Promise.resolve(alertParams));
spyOn(AlertsService.prototype, 'updateAlert').and.returnValue(
Promise.resolve({ ...alertParams, ...newAlertParams }),
);
vm = mountComponent(AlertWidgetComponent, propsWithAlertData);
vm.$on('setAlerts', mockSetAlerts);
vm.$refs.widgetForm.$emit('update', {
alert: alertPath,
...newAlertParams,
prometheus_metric_id: '5',
});
expect(AlertsService.prototype.updateAlert).toHaveBeenCalledWith(alertPath, newAlertParams);
waitForPromises()
.then(() => {
expect(vm.isLoading).toEqual(false);
done();
})
.catch(done.fail);
});
it('deletes an alert with an appropriate handler', done => {
const alertParams = { alert_path: alertPath, operator: '>', threshold: 42 };
spyOn(AlertsService.prototype, 'readAlert').and.returnValue(Promise.resolve(alertParams));
spyOn(AlertsService.prototype, 'deleteAlert').and.returnValue(Promise.resolve({}));
vm = mountComponent(AlertWidgetComponent, propsWithAlert);
vm.$on('setAlerts', mockSetAlerts);
vm.$refs.widgetForm.$emit('delete', { alert: alertPath });
expect(AlertsService.prototype.deleteAlert).toHaveBeenCalledWith(alertPath);
waitForPromises()
.then(() => {
expect(vm.isLoading).toEqual(false);
expect(vm.alertSummary).toBeFalsy();
done();
})
.catch(done.fail);
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment