Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ae30db7b18
commit
6b2c5f542b
14 changed files with 370 additions and 36 deletions
62
app/assets/javascripts/api/alert_management_alerts_api.js
Normal file
62
app/assets/javascripts/api/alert_management_alerts_api.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
import { buildApiUrl } from '~/api/api_utils';
|
||||
import { ContentTypeMultipartFormData } from '~/lib/utils/headers';
|
||||
|
||||
const ALERT_METRIC_IMAGES_PATH =
|
||||
'/api/:version/projects/:id/alert_management_alerts/:alert_iid/metric_images';
|
||||
const ALERT_SINGLE_METRIC_IMAGE_PATH =
|
||||
'/api/:version/projects/:id/alert_management_alerts/:alert_iid/metric_images/:image_id';
|
||||
|
||||
export function fetchAlertMetricImages({ alertIid, id }) {
|
||||
const metricImagesUrl = buildApiUrl(ALERT_METRIC_IMAGES_PATH)
|
||||
.replace(':id', encodeURIComponent(id))
|
||||
.replace(':alert_iid', encodeURIComponent(alertIid));
|
||||
|
||||
return axios.get(metricImagesUrl);
|
||||
}
|
||||
|
||||
export function uploadAlertMetricImage({ alertIid, id, file, url = null, urlText = null }) {
|
||||
const options = { headers: { ...ContentTypeMultipartFormData } };
|
||||
const metricImagesUrl = buildApiUrl(ALERT_METRIC_IMAGES_PATH)
|
||||
.replace(':id', encodeURIComponent(id))
|
||||
.replace(':alert_iid', encodeURIComponent(alertIid));
|
||||
|
||||
// Construct multipart form data
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
if (url) {
|
||||
formData.append('url', url);
|
||||
}
|
||||
if (urlText) {
|
||||
formData.append('url_text', urlText);
|
||||
}
|
||||
|
||||
return axios.post(metricImagesUrl, formData, options);
|
||||
}
|
||||
|
||||
export function updateAlertMetricImage({ alertIid, id, imageId, url = null, urlText = null }) {
|
||||
const metricImagesUrl = buildApiUrl(ALERT_SINGLE_METRIC_IMAGE_PATH)
|
||||
.replace(':id', encodeURIComponent(id))
|
||||
.replace(':alert_iid', encodeURIComponent(alertIid))
|
||||
.replace(':image_id', encodeURIComponent(imageId));
|
||||
|
||||
// Construct multipart form data
|
||||
const formData = new FormData();
|
||||
if (url != null) {
|
||||
formData.append('url', url);
|
||||
}
|
||||
if (urlText != null) {
|
||||
formData.append('url_text', urlText);
|
||||
}
|
||||
|
||||
return axios.put(metricImagesUrl, formData);
|
||||
}
|
||||
|
||||
export function deleteAlertMetricImage({ alertIid, id, imageId }) {
|
||||
const individualMetricImageUrl = buildApiUrl(ALERT_SINGLE_METRIC_IMAGE_PATH)
|
||||
.replace(':id', encodeURIComponent(id))
|
||||
.replace(':alert_iid', encodeURIComponent(alertIid))
|
||||
.replace(':image_id', encodeURIComponent(imageId));
|
||||
|
||||
return axios.delete(individualMetricImageUrl);
|
||||
}
|
|
@ -2,6 +2,8 @@ import { DEFAULT_PER_PAGE } from '~/api';
|
|||
import axios from '../lib/utils/axios_utils';
|
||||
import { buildApiUrl } from './api_utils';
|
||||
|
||||
export * from './alert_management_alerts_api';
|
||||
|
||||
const PROJECTS_PATH = '/api/:version/projects.json';
|
||||
const PROJECT_IMPORT_MEMBERS_PATH = '/api/:version/projects/:id/import_project_members/:project_id';
|
||||
|
||||
|
|
|
@ -21,12 +21,12 @@ import Tracking from '~/tracking';
|
|||
import initUserPopovers from '~/user_popovers';
|
||||
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import MetricImagesTab from '~/vue_shared/components/metric_images/metric_images_tab.vue';
|
||||
import { PAGE_CONFIG, SEVERITY_LEVELS } from '../constants';
|
||||
import createIssueMutation from '../graphql/mutations/alert_issue_create.mutation.graphql';
|
||||
import toggleSidebarStatusMutation from '../graphql/mutations/alert_sidebar_status.mutation.graphql';
|
||||
import alertQuery from '../graphql/queries/alert_sidebar_details.query.graphql';
|
||||
import sidebarStatusQuery from '../graphql/queries/alert_sidebar_status.query.graphql';
|
||||
import AlertMetrics from './alert_metrics.vue';
|
||||
import AlertSidebar from './alert_sidebar.vue';
|
||||
import AlertSummaryRow from './alert_summary_row.vue';
|
||||
import SystemNote from './system_notes/system_note.vue';
|
||||
|
@ -74,7 +74,7 @@ export default {
|
|||
TimeAgoTooltip,
|
||||
AlertSidebar,
|
||||
SystemNote,
|
||||
AlertMetrics,
|
||||
MetricImagesTab,
|
||||
},
|
||||
inject: {
|
||||
projectPath: {
|
||||
|
@ -372,13 +372,12 @@ export default {
|
|||
</alert-summary-row>
|
||||
<alert-details-table :alert="alert" :loading="loading" :statuses="statuses" />
|
||||
</gl-tab>
|
||||
<gl-tab
|
||||
|
||||
<metric-images-tab
|
||||
v-if="!isThreatMonitoringPage"
|
||||
:data-testid="$options.tabsConfig[1].id"
|
||||
:title="$options.tabsConfig[1].title"
|
||||
>
|
||||
<alert-metrics :dashboard-url="alert.metricsDashboardUrl" />
|
||||
</gl-tab>
|
||||
/>
|
||||
<gl-tab :data-testid="$options.tabsConfig[2].id" :title="$options.tabsConfig[2].title">
|
||||
<div v-if="alert.notes.nodes.length > 0" class="issuable-discussion">
|
||||
<ul class="notes main-notes-list timeline">
|
||||
|
|
|
@ -3,6 +3,9 @@ import produce from 'immer';
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import createStore from '~/vue_shared/components/metric_images/store';
|
||||
import service from './service';
|
||||
import AlertDetails from './components/alert_details.vue';
|
||||
import { PAGE_CONFIG } from './constants';
|
||||
import sidebarStatusQuery from './graphql/queries/alert_sidebar_status.query.graphql';
|
||||
|
@ -12,7 +15,8 @@ Vue.use(VueApollo);
|
|||
|
||||
export default (selector) => {
|
||||
const domEl = document.querySelector(selector);
|
||||
const { alertId, projectPath, projectIssuesPath, projectId, page } = domEl.dataset;
|
||||
const { alertId, projectPath, projectIssuesPath, projectId, page, canUpdate } = domEl.dataset;
|
||||
const iid = alertId;
|
||||
const router = createRouter();
|
||||
|
||||
const resolvers = {
|
||||
|
@ -54,15 +58,20 @@ export default (selector) => {
|
|||
page,
|
||||
projectIssuesPath,
|
||||
projectId,
|
||||
iid,
|
||||
statuses: PAGE_CONFIG[page].STATUSES,
|
||||
canUpdate: parseBoolean(canUpdate),
|
||||
};
|
||||
|
||||
const opsProperties = {};
|
||||
|
||||
if (page === PAGE_CONFIG.OPERATIONS.TITLE) {
|
||||
const { TRACK_ALERTS_DETAILS_VIEWS_OPTIONS, TRACK_ALERT_STATUS_UPDATE_OPTIONS } = PAGE_CONFIG[
|
||||
page
|
||||
];
|
||||
provide.trackAlertsDetailsViewsOptions = TRACK_ALERTS_DETAILS_VIEWS_OPTIONS;
|
||||
provide.trackAlertStatusUpdateOptions = TRACK_ALERT_STATUS_UPDATE_OPTIONS;
|
||||
opsProperties.store = createStore({}, service);
|
||||
} else if (page === PAGE_CONFIG.THREAT_MONITORING.TITLE) {
|
||||
provide.isThreatMonitoringPage = true;
|
||||
}
|
||||
|
@ -74,6 +83,7 @@ export default (selector) => {
|
|||
components: {
|
||||
AlertDetails,
|
||||
},
|
||||
...opsProperties,
|
||||
provide,
|
||||
apolloProvider,
|
||||
router,
|
||||
|
|
43
app/assets/javascripts/vue_shared/alert_details/service.js
Normal file
43
app/assets/javascripts/vue_shared/alert_details/service.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
import {
|
||||
fetchAlertMetricImages,
|
||||
uploadAlertMetricImage,
|
||||
updateAlertMetricImage,
|
||||
deleteAlertMetricImage,
|
||||
} from '~/rest_api';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
|
||||
function replaceModelIId(payload = {}) {
|
||||
delete Object.assign(payload, { alertIid: payload.modelIid }).modelIid;
|
||||
return payload;
|
||||
}
|
||||
|
||||
export const getMetricImages = async (payload) => {
|
||||
const apiPayload = replaceModelIId(payload);
|
||||
const response = await fetchAlertMetricImages(apiPayload);
|
||||
return convertObjectPropsToCamelCase(response.data, { deep: true });
|
||||
};
|
||||
|
||||
export const uploadMetricImage = async (payload) => {
|
||||
const apiPayload = replaceModelIId(payload);
|
||||
const response = await uploadAlertMetricImage(apiPayload);
|
||||
return convertObjectPropsToCamelCase(response.data);
|
||||
};
|
||||
|
||||
export const updateMetricImage = async (payload) => {
|
||||
const apiPayload = replaceModelIId(payload);
|
||||
const response = await updateAlertMetricImage(apiPayload);
|
||||
return convertObjectPropsToCamelCase(response.data);
|
||||
};
|
||||
|
||||
export const deleteMetricImage = async (payload) => {
|
||||
const apiPayload = replaceModelIId(payload);
|
||||
const response = await deleteAlertMetricImage(apiPayload);
|
||||
return convertObjectPropsToCamelCase(response.data);
|
||||
};
|
||||
|
||||
export default {
|
||||
getMetricImages,
|
||||
uploadMetricImage,
|
||||
updateMetricImage,
|
||||
deleteMetricImage,
|
||||
};
|
|
@ -15,13 +15,14 @@ module Projects::AlertManagementHelper
|
|||
}
|
||||
end
|
||||
|
||||
def alert_management_detail_data(project, alert_id)
|
||||
def alert_management_detail_data(current_user, project, alert_id)
|
||||
{
|
||||
'alert-id' => alert_id,
|
||||
'project-path' => project.full_path,
|
||||
'project-id' => project.id,
|
||||
'project-issues-path' => project_issues_path(project),
|
||||
'page' => 'OPERATIONS'
|
||||
'page' => 'OPERATIONS',
|
||||
'can-update' => can?(current_user, :update_alert_management_alert, project).to_s
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-terms-settings'), html: { class: 'fieldset-form', id: 'terms-settings' } do |f|
|
||||
= gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-terms-settings'), html: { class: 'fieldset-form', id: 'terms-settings' } do |f|
|
||||
= form_errors(@application_setting)
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
.form-check
|
||||
= f.check_box :enforce_terms, class: 'form-check-input'
|
||||
= f.label :enforce_terms, class: 'form-check-label' do
|
||||
= _("All users must accept the Terms of Service and Privacy Policy to access GitLab")
|
||||
.form-text.text-muted
|
||||
= f.gitlab_ui_checkbox_component :enforce_terms, _("All users must accept the Terms of Service and Privacy Policy to access GitLab")
|
||||
.form-group
|
||||
= f.label :terms do
|
||||
= _("Terms of Service Agreement and Privacy Policy")
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
- page_title s_('AlertManagement|Alert detail')
|
||||
- add_page_specific_style 'page_bundles/alert_management_details'
|
||||
|
||||
#js-alert_details{ data: alert_management_detail_data(@project, @alert_id) }
|
||||
#js-alert_details{ data: alert_management_detail_data(current_user, @project, @alert_id) }
|
||||
|
|
|
@ -86,26 +86,22 @@ The **Alert details** tab has two sections. The top section provides a short lis
|
|||
|
||||
### Metrics tab
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217768) in GitLab 13.2.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217768) in GitLab 13.2.
|
||||
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/340852) in GitLab 14.10. In GitLab 14.9 and earlier, this tab shows a metrics chart for alerts coming from Prometheus.
|
||||
|
||||
The **Metrics** tab displays a metrics chart for alerts coming from Prometheus. If the alert originated from any other tool, the **Metrics** tab is empty.
|
||||
For externally-managed Prometheus instances, you must configure your alerting rules to display a chart in the alert. For information about how to configure
|
||||
your alerting rules, see [Embedding metrics based on alerts in incident issues](../metrics/embed.md#embedding-metrics-based-on-alerts-in-incident-issues). See
|
||||
[External Prometheus instances](../metrics/alerts.md#external-prometheus-instances) for information about setting up alerts for your self-managed Prometheus
|
||||
instance.
|
||||
In many cases, alerts are associated to metrics. You can upload screenshots of metric
|
||||
charts in the **Metrics** tab.
|
||||
|
||||
Prerequisite:
|
||||
To do so, either:
|
||||
|
||||
- You must have at least the Developer role.
|
||||
- Select **upload** and then select an image from your file browser.
|
||||
- Drag a file from your file browser and drop it in the drop zone.
|
||||
|
||||
To view the metrics for an alert:
|
||||
When you upload an image, you can add text to the image and link it to the original graph.
|
||||
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Monitor > Alerts**.
|
||||
1. Select the alert you want to view.
|
||||
1. Below the title of the alert, select the **Metrics** tab.
|
||||
![Text link modal](img/incident_metrics_tab_text_link_modal_v14_9.png)
|
||||
|
||||
![Alert Metrics View](img/alert_detail_metrics_v13_2.png)
|
||||
If you add a link, it is shown above the uploaded image.
|
||||
|
||||
#### View an alert's logs
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 27 KiB |
140
spec/frontend/api/alert_management_alerts_api_spec.js
Normal file
140
spec/frontend/api/alert_management_alerts_api_spec.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import * as alertManagementAlertsApi from '~/api/alert_management_alerts_api';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
describe('~/api/alert_management_alerts_api.js', () => {
|
||||
let mock;
|
||||
let originalGon;
|
||||
|
||||
const projectId = 1;
|
||||
const alertIid = 2;
|
||||
|
||||
const imageData = { filePath: 'test', filename: 'hello', id: 5, url: null };
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
|
||||
originalGon = window.gon;
|
||||
window.gon = { api_version: 'v4' };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
window.gon = originalGon;
|
||||
});
|
||||
|
||||
describe('fetchAlertMetricImages', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(axios, 'get');
|
||||
});
|
||||
|
||||
it('retrieves metric images from the correct URL and returns them in the response data', () => {
|
||||
const expectedUrl = `/api/v4/projects/${projectId}/alert_management_alerts/${alertIid}/metric_images`;
|
||||
const expectedData = [imageData];
|
||||
const options = { alertIid, id: projectId };
|
||||
|
||||
mock.onGet(expectedUrl).reply(200, { data: expectedData });
|
||||
|
||||
return alertManagementAlertsApi.fetchAlertMetricImages(options).then(({ data }) => {
|
||||
expect(axios.get).toHaveBeenCalledWith(expectedUrl);
|
||||
expect(data.data).toEqual(expectedData);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('uploadAlertMetricImage', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(axios, 'post');
|
||||
});
|
||||
|
||||
it('uploads a metric image to the correct URL and returns it in the response data', () => {
|
||||
const expectedUrl = `/api/v4/projects/${projectId}/alert_management_alerts/${alertIid}/metric_images`;
|
||||
const expectedData = [imageData];
|
||||
|
||||
const file = new File(['zip contents'], 'hello');
|
||||
const url = 'https://www.example.com';
|
||||
const urlText = 'Example website';
|
||||
|
||||
const expectedFormData = new FormData();
|
||||
expectedFormData.append('file', file);
|
||||
expectedFormData.append('url', url);
|
||||
expectedFormData.append('url_text', urlText);
|
||||
|
||||
mock.onPost(expectedUrl).reply(201, { data: expectedData });
|
||||
|
||||
return alertManagementAlertsApi
|
||||
.uploadAlertMetricImage({
|
||||
alertIid,
|
||||
id: projectId,
|
||||
file,
|
||||
url,
|
||||
urlText,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
expect(data).toEqual({ data: expectedData });
|
||||
expect(axios.post).toHaveBeenCalledWith(expectedUrl, expectedFormData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateAlertMetricImage', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(axios, 'put');
|
||||
});
|
||||
|
||||
it('updates a metric image to the correct URL and returns it in the response data', () => {
|
||||
const imageIid = 3;
|
||||
const expectedUrl = `/api/v4/projects/${projectId}/alert_management_alerts/${alertIid}/metric_images/${imageIid}`;
|
||||
const expectedData = [imageData];
|
||||
|
||||
const url = 'https://www.example.com';
|
||||
const urlText = 'Example website';
|
||||
|
||||
const expectedFormData = new FormData();
|
||||
expectedFormData.append('url', url);
|
||||
expectedFormData.append('url_text', urlText);
|
||||
|
||||
mock.onPut(expectedUrl).reply(200, { data: expectedData });
|
||||
|
||||
return alertManagementAlertsApi
|
||||
.updateAlertMetricImage({
|
||||
alertIid,
|
||||
id: projectId,
|
||||
imageId: imageIid,
|
||||
url,
|
||||
urlText,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
expect(data).toEqual({ data: expectedData });
|
||||
expect(axios.put).toHaveBeenCalledWith(expectedUrl, expectedFormData);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteAlertMetricImage', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(axios, 'delete');
|
||||
});
|
||||
|
||||
it('deletes a metric image to the correct URL and returns it in the response data', () => {
|
||||
const imageIid = 3;
|
||||
const expectedUrl = `/api/v4/projects/${projectId}/alert_management_alerts/${alertIid}/metric_images/${imageIid}`;
|
||||
const expectedData = [imageData];
|
||||
|
||||
mock.onDelete(expectedUrl).reply(204, { data: expectedData });
|
||||
|
||||
return alertManagementAlertsApi
|
||||
.deleteAlertMetricImage({
|
||||
alertIid,
|
||||
id: projectId,
|
||||
imageId: imageIid,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
expect(data).toEqual({ data: expectedData });
|
||||
expect(axios.delete).toHaveBeenCalledWith(expectedUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -12,12 +12,17 @@ import AlertSummaryRow from '~/vue_shared/alert_details/components/alert_summary
|
|||
import { PAGE_CONFIG, SEVERITY_LEVELS } from '~/vue_shared/alert_details/constants';
|
||||
import createIssueMutation from '~/vue_shared/alert_details/graphql/mutations/alert_issue_create.mutation.graphql';
|
||||
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
|
||||
import MetricImagesTab from '~/vue_shared/components/metric_images/metric_images_tab.vue';
|
||||
import createStore from '~/vue_shared/components/metric_images/store/';
|
||||
import service from '~/vue_shared/alert_details/service';
|
||||
import mockAlerts from './mocks/alerts.json';
|
||||
|
||||
const mockAlert = mockAlerts[0];
|
||||
const environmentName = 'Production';
|
||||
const environmentPath = '/fake/path';
|
||||
|
||||
jest.mock('~/vue_shared/alert_details/service');
|
||||
|
||||
describe('AlertDetails', () => {
|
||||
let environmentData = { name: environmentName, path: environmentPath };
|
||||
let mock;
|
||||
|
@ -67,9 +72,11 @@ describe('AlertDetails', () => {
|
|||
$route: { params: {} },
|
||||
},
|
||||
stubs: {
|
||||
...stubs,
|
||||
AlertSummaryRow,
|
||||
'metric-images-tab': true,
|
||||
...stubs,
|
||||
},
|
||||
store: createStore({}, service),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -91,7 +98,7 @@ describe('AlertDetails', () => {
|
|||
const findEnvironmentName = () => wrapper.findByTestId('environmentName');
|
||||
const findEnvironmentPath = () => wrapper.findByTestId('environmentPath');
|
||||
const findDetailsTable = () => wrapper.findComponent(AlertDetailsTable);
|
||||
const findMetricsTab = () => wrapper.findByTestId('metrics');
|
||||
const findMetricsTab = () => wrapper.findComponent(MetricImagesTab);
|
||||
|
||||
describe('Alert details', () => {
|
||||
describe('when alert is null', () => {
|
||||
|
@ -129,8 +136,21 @@ describe('AlertDetails', () => {
|
|||
expect(wrapper.findByTestId('startTimeItem').exists()).toBe(true);
|
||||
expect(wrapper.findByTestId('startTimeItem').props('time')).toBe(mockAlert.startedAt);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Metrics tab', () => {
|
||||
it('should mount without errors', () => {
|
||||
mountComponent({
|
||||
mountMethod: mount,
|
||||
provide: {
|
||||
canUpdate: true,
|
||||
iid: '1',
|
||||
},
|
||||
stubs: {
|
||||
MetricImagesTab,
|
||||
},
|
||||
});
|
||||
|
||||
it('renders the metrics tab', () => {
|
||||
expect(findMetricsTab().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
@ -312,7 +332,9 @@ describe('AlertDetails', () => {
|
|||
|
||||
describe('header', () => {
|
||||
const findHeader = () => wrapper.findByTestId('alert-header');
|
||||
const stubs = { TimeAgoTooltip: { template: '<span>now</span>' } };
|
||||
const stubs = {
|
||||
TimeAgoTooltip: { template: '<span>now</span>' },
|
||||
};
|
||||
|
||||
describe('individual header fields', () => {
|
||||
describe.each`
|
||||
|
|
44
spec/frontend/vue_shared/alert_details/service_spec.js
Normal file
44
spec/frontend/vue_shared/alert_details/service_spec.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { fileList, fileListRaw } from 'jest/vue_shared/components/metric_images/mock_data';
|
||||
import {
|
||||
getMetricImages,
|
||||
uploadMetricImage,
|
||||
updateMetricImage,
|
||||
deleteMetricImage,
|
||||
} from '~/vue_shared/alert_details/service';
|
||||
import * as alertManagementAlertsApi from '~/api/alert_management_alerts_api';
|
||||
|
||||
jest.mock('~/api/alert_management_alerts_api');
|
||||
|
||||
describe('Alert details service', () => {
|
||||
it('fetches metric images', async () => {
|
||||
alertManagementAlertsApi.fetchAlertMetricImages.mockResolvedValue({ data: fileListRaw });
|
||||
const result = await getMetricImages();
|
||||
|
||||
expect(alertManagementAlertsApi.fetchAlertMetricImages).toHaveBeenCalled();
|
||||
expect(result).toEqual(fileList);
|
||||
});
|
||||
|
||||
it('uploads a metric image', async () => {
|
||||
alertManagementAlertsApi.uploadAlertMetricImage.mockResolvedValue({ data: fileListRaw[0] });
|
||||
const result = await uploadMetricImage();
|
||||
|
||||
expect(alertManagementAlertsApi.uploadAlertMetricImage).toHaveBeenCalled();
|
||||
expect(result).toEqual(fileList[0]);
|
||||
});
|
||||
|
||||
it('updates a metric image', async () => {
|
||||
alertManagementAlertsApi.updateAlertMetricImage.mockResolvedValue({ data: fileListRaw[0] });
|
||||
const result = await updateMetricImage();
|
||||
|
||||
expect(alertManagementAlertsApi.updateAlertMetricImage).toHaveBeenCalled();
|
||||
expect(result).toEqual(fileList[0]);
|
||||
});
|
||||
|
||||
it('deletes a metric image', async () => {
|
||||
alertManagementAlertsApi.deleteAlertMetricImage.mockResolvedValue({ data: '' });
|
||||
const result = await deleteMetricImage();
|
||||
|
||||
expect(alertManagementAlertsApi.deleteAlertMetricImage).toHaveBeenCalled();
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
|
@ -110,15 +110,34 @@ RSpec.describe Projects::AlertManagementHelper do
|
|||
describe '#alert_management_detail_data' do
|
||||
let(:alert_id) { 1 }
|
||||
let(:issues_path) { project_issues_path(project) }
|
||||
let(:can_update_alert) { true }
|
||||
|
||||
before do
|
||||
allow(helper)
|
||||
.to receive(:can?)
|
||||
.with(current_user, :update_alert_management_alert, project)
|
||||
.and_return(can_update_alert)
|
||||
end
|
||||
|
||||
it 'returns detail page configuration' do
|
||||
expect(helper.alert_management_detail_data(project, alert_id)).to eq(
|
||||
expect(helper.alert_management_detail_data(current_user, project, alert_id)).to eq(
|
||||
'alert-id' => alert_id,
|
||||
'project-path' => project_path,
|
||||
'project-id' => project_id,
|
||||
'project-issues-path' => issues_path,
|
||||
'page' => 'OPERATIONS'
|
||||
'page' => 'OPERATIONS',
|
||||
'can-update' => 'true'
|
||||
)
|
||||
end
|
||||
|
||||
context 'when user cannot update alert' do
|
||||
let(:can_update_alert) { false }
|
||||
|
||||
it 'shows error tracking enablement as disabled' do
|
||||
expect(helper.alert_management_detail_data(current_user, project, alert_id)).to include(
|
||||
'can-update' => 'false'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue