379 lines
12 KiB
Vue
379 lines
12 KiB
Vue
<script>
|
|
import {
|
|
GlAlert,
|
|
GlBadge,
|
|
GlIcon,
|
|
GlLink,
|
|
GlLoadingIcon,
|
|
GlSprintf,
|
|
GlTabs,
|
|
GlTab,
|
|
GlButton,
|
|
GlSafeHtmlDirective,
|
|
} from '@gitlab/ui';
|
|
import * as Sentry from '~/sentry/wrapper';
|
|
import { s__ } from '~/locale';
|
|
import alertQuery from '../graphql/queries/details.query.graphql';
|
|
import sidebarStatusQuery from '../graphql/queries/sidebar_status.query.graphql';
|
|
import { fetchPolicies } from '~/lib/graphql';
|
|
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
|
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
|
|
import initUserPopovers from '~/user_popovers';
|
|
import { ALERTS_SEVERITY_LABELS, trackAlertsDetailsViewsOptions } from '../constants';
|
|
import createIssueMutation from '../graphql/mutations/create_issue_from_alert.mutation.graphql';
|
|
import toggleSidebarStatusMutation from '../graphql/mutations/toggle_sidebar_status.mutation.graphql';
|
|
import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
|
|
import Tracking from '~/tracking';
|
|
import { toggleContainerClasses } from '~/lib/utils/dom_utils';
|
|
import SystemNote from './system_notes/system_note.vue';
|
|
import AlertSidebar from './alert_sidebar.vue';
|
|
import AlertMetrics from './alert_metrics.vue';
|
|
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
|
|
import AlertSummaryRow from './alert_summary_row.vue';
|
|
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
|
|
|
const containerEl = document.querySelector('.page-with-contextual-sidebar');
|
|
|
|
export default {
|
|
i18n: {
|
|
errorMsg: s__(
|
|
'AlertManagement|There was an error displaying the alert. Please refresh the page to try again.',
|
|
),
|
|
reportedAt: s__('AlertManagement|Reported %{when}'),
|
|
reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'),
|
|
},
|
|
directives: {
|
|
SafeHtml: GlSafeHtmlDirective,
|
|
},
|
|
severityLabels: ALERTS_SEVERITY_LABELS,
|
|
tabsConfig: [
|
|
{
|
|
id: 'overview',
|
|
title: s__('AlertManagement|Alert details'),
|
|
},
|
|
{
|
|
id: 'metrics',
|
|
title: s__('AlertManagement|Metrics'),
|
|
},
|
|
{
|
|
id: 'activity',
|
|
title: s__('AlertManagement|Activity feed'),
|
|
},
|
|
],
|
|
components: {
|
|
AlertDetailsTable,
|
|
AlertSummaryRow,
|
|
GlBadge,
|
|
GlAlert,
|
|
GlIcon,
|
|
GlLink,
|
|
GlLoadingIcon,
|
|
GlSprintf,
|
|
GlTab,
|
|
GlTabs,
|
|
GlButton,
|
|
TimeAgoTooltip,
|
|
AlertSidebar,
|
|
SystemNote,
|
|
AlertMetrics,
|
|
},
|
|
mixins: [glFeatureFlagsMixin()],
|
|
inject: {
|
|
projectPath: {
|
|
default: '',
|
|
},
|
|
alertId: {
|
|
default: '',
|
|
},
|
|
projectId: {
|
|
default: '',
|
|
},
|
|
projectIssuesPath: {
|
|
default: '',
|
|
},
|
|
},
|
|
apollo: {
|
|
alert: {
|
|
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
|
|
query: alertQuery,
|
|
variables() {
|
|
return {
|
|
fullPath: this.projectPath,
|
|
alertId: this.alertId,
|
|
};
|
|
},
|
|
update(data) {
|
|
return data?.project?.alertManagementAlerts?.nodes?.[0] ?? null;
|
|
},
|
|
error(error) {
|
|
this.errored = true;
|
|
Sentry.captureException(error);
|
|
},
|
|
},
|
|
sidebarStatus: {
|
|
query: sidebarStatusQuery,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
alert: null,
|
|
errored: false,
|
|
sidebarStatus: false,
|
|
isErrorDismissed: false,
|
|
createIncidentError: '',
|
|
incidentCreationInProgress: false,
|
|
sidebarErrorMessage: '',
|
|
};
|
|
},
|
|
computed: {
|
|
loading() {
|
|
return this.$apollo.queries.alert.loading;
|
|
},
|
|
reportedAtMessage() {
|
|
return this.alert?.monitoringTool
|
|
? this.$options.i18n.reportedAtWithTool
|
|
: this.$options.i18n.reportedAt;
|
|
},
|
|
showErrorMsg() {
|
|
return this.errored && !this.isErrorDismissed;
|
|
},
|
|
activeTab() {
|
|
return this.$route.params.tabId || this.$options.tabsConfig[0].id;
|
|
},
|
|
currentTabIndex: {
|
|
get() {
|
|
return this.$options.tabsConfig.findIndex(tab => tab.id === this.activeTab);
|
|
},
|
|
set(tabIdx) {
|
|
const tabId = this.$options.tabsConfig[tabIdx].id;
|
|
this.$router.replace({ name: 'tab', params: { tabId } });
|
|
},
|
|
},
|
|
environmentName() {
|
|
return this.shouldDisplayEnvironment && this.alert?.environment?.name;
|
|
},
|
|
environmentPath() {
|
|
return this.shouldDisplayEnvironment && this.alert?.environment?.path;
|
|
},
|
|
shouldDisplayEnvironment() {
|
|
return this.glFeatures.exposeEnvironmentPathInAlertDetails;
|
|
},
|
|
},
|
|
mounted() {
|
|
this.trackPageViews();
|
|
toggleContainerClasses(containerEl, {
|
|
'issuable-bulk-update-sidebar': true,
|
|
'right-sidebar-expanded': true,
|
|
});
|
|
},
|
|
updated() {
|
|
this.$nextTick(() => {
|
|
highlightCurrentUser(this.$el.querySelectorAll('.gfm-project_member'));
|
|
initUserPopovers(this.$el.querySelectorAll('.js-user-link'));
|
|
});
|
|
},
|
|
methods: {
|
|
dismissError() {
|
|
this.isErrorDismissed = true;
|
|
this.sidebarErrorMessage = '';
|
|
},
|
|
toggleSidebar() {
|
|
this.$apollo.mutate({ mutation: toggleSidebarStatusMutation });
|
|
toggleContainerClasses(containerEl, {
|
|
'right-sidebar-collapsed': !this.sidebarStatus,
|
|
'right-sidebar-expanded': this.sidebarStatus,
|
|
});
|
|
},
|
|
handleAlertSidebarError(errorMessage) {
|
|
this.errored = true;
|
|
this.sidebarErrorMessage = errorMessage;
|
|
},
|
|
createIncident() {
|
|
this.incidentCreationInProgress = true;
|
|
|
|
this.$apollo
|
|
.mutate({
|
|
mutation: createIssueMutation,
|
|
variables: {
|
|
iid: this.alert.iid,
|
|
projectPath: this.projectPath,
|
|
},
|
|
})
|
|
.then(({ data: { createAlertIssue: { errors, issue } } }) => {
|
|
if (errors?.length) {
|
|
[this.createIncidentError] = errors;
|
|
this.incidentCreationInProgress = false;
|
|
} else if (issue) {
|
|
visitUrl(this.incidentPath(issue.iid));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
this.createIncidentError = error;
|
|
this.incidentCreationInProgress = false;
|
|
});
|
|
},
|
|
incidentPath(issueId) {
|
|
return joinPaths(this.projectIssuesPath, issueId);
|
|
},
|
|
trackPageViews() {
|
|
const { category, action } = trackAlertsDetailsViewsOptions;
|
|
Tracking.event(category, action);
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<gl-alert v-if="showErrorMsg" variant="danger" @dismiss="dismissError">
|
|
<p v-safe-html="sidebarErrorMessage || $options.i18n.errorMsg"></p>
|
|
</gl-alert>
|
|
<gl-alert
|
|
v-if="createIncidentError"
|
|
variant="danger"
|
|
data-testid="incidentCreationError"
|
|
@dismiss="createIncidentError = null"
|
|
>
|
|
{{ createIncidentError }}
|
|
</gl-alert>
|
|
<div v-if="loading"><gl-loading-icon size="lg" class="gl-mt-5" /></div>
|
|
<div
|
|
v-if="alert"
|
|
class="alert-management-details gl-relative"
|
|
:class="{ 'pr-sm-8': sidebarStatus }"
|
|
>
|
|
<div
|
|
class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-px-1 py-3 py-md-4 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-flex-direction-column gl-sm-flex-direction-row"
|
|
>
|
|
<div data-testid="alert-header">
|
|
<gl-badge class="gl-mr-3">
|
|
<strong>{{ s__('AlertManagement|Alert') }}</strong>
|
|
</gl-badge>
|
|
<span>
|
|
<gl-sprintf :message="reportedAtMessage">
|
|
<template #when>
|
|
<time-ago-tooltip :time="alert.createdAt" />
|
|
</template>
|
|
<template #tool>{{ alert.monitoringTool }}</template>
|
|
</gl-sprintf>
|
|
</span>
|
|
</div>
|
|
<gl-button
|
|
v-if="alert.issueIid"
|
|
class="gl-mt-3 mt-sm-0 align-self-center align-self-sm-baseline alert-details-incident-button"
|
|
data-testid="viewIncidentBtn"
|
|
:href="incidentPath(alert.issueIid)"
|
|
category="primary"
|
|
variant="success"
|
|
>
|
|
{{ s__('AlertManagement|View incident') }}
|
|
</gl-button>
|
|
<gl-button
|
|
v-else
|
|
class="gl-mt-3 mt-sm-0 align-self-center align-self-sm-baseline alert-details-incident-button"
|
|
data-testid="createIncidentBtn"
|
|
:loading="incidentCreationInProgress"
|
|
category="primary"
|
|
variant="success"
|
|
@click="createIncident()"
|
|
>
|
|
{{ s__('AlertManagement|Create incident') }}
|
|
</gl-button>
|
|
<gl-button
|
|
:aria-label="__('Toggle sidebar')"
|
|
category="primary"
|
|
variant="default"
|
|
class="d-sm-none gl-absolute toggle-sidebar-mobile-button"
|
|
type="button"
|
|
icon="chevron-double-lg-left"
|
|
@click="toggleSidebar"
|
|
/>
|
|
</div>
|
|
<div
|
|
v-if="alert"
|
|
class="gl-display-flex gl-justify-content-space-between gl-align-items-center"
|
|
>
|
|
<h2 data-testid="title">{{ alert.title }}</h2>
|
|
</div>
|
|
<gl-tabs v-if="alert" v-model="currentTabIndex" data-testid="alertDetailsTabs">
|
|
<gl-tab :data-testid="$options.tabsConfig[0].id" :title="$options.tabsConfig[0].title">
|
|
<alert-summary-row v-if="alert.severity" :label="`${s__('AlertManagement|Severity')}:`">
|
|
<span data-testid="severity">
|
|
<gl-icon
|
|
class="gl-vertical-align-middle"
|
|
:size="12"
|
|
:name="`severity-${alert.severity.toLowerCase()}`"
|
|
:class="`icon-${alert.severity.toLowerCase()}`"
|
|
/>
|
|
{{ $options.severityLabels[alert.severity] }}
|
|
</span>
|
|
</alert-summary-row>
|
|
<alert-summary-row
|
|
v-if="environmentName"
|
|
:label="`${s__('AlertManagement|Environment')}:`"
|
|
>
|
|
<gl-link
|
|
v-if="environmentPath"
|
|
class="gl-display-inline-block"
|
|
data-testid="environmentPath"
|
|
:href="environmentPath"
|
|
>
|
|
{{ environmentName }}
|
|
</gl-link>
|
|
<span v-else data-testid="environmentName">{{ environmentName }}</span>
|
|
</alert-summary-row>
|
|
<alert-summary-row
|
|
v-if="alert.startedAt"
|
|
:label="`${s__('AlertManagement|Start time')}:`"
|
|
>
|
|
<time-ago-tooltip data-testid="startTimeItem" :time="alert.startedAt" />
|
|
</alert-summary-row>
|
|
<alert-summary-row
|
|
v-if="alert.eventCount"
|
|
:label="`${s__('AlertManagement|Events')}:`"
|
|
data-testid="eventCount"
|
|
>
|
|
{{ alert.eventCount }}
|
|
</alert-summary-row>
|
|
<alert-summary-row
|
|
v-if="alert.monitoringTool"
|
|
:label="`${s__('AlertManagement|Tool')}:`"
|
|
data-testid="monitoringTool"
|
|
>
|
|
{{ alert.monitoringTool }}
|
|
</alert-summary-row>
|
|
<alert-summary-row
|
|
v-if="alert.service"
|
|
:label="`${s__('AlertManagement|Service')}:`"
|
|
data-testid="service"
|
|
>
|
|
{{ alert.service }}
|
|
</alert-summary-row>
|
|
<alert-summary-row
|
|
v-if="alert.runbook"
|
|
:label="`${s__('AlertManagement|Runbook')}:`"
|
|
data-testid="runbook"
|
|
>
|
|
{{ alert.runbook }}
|
|
</alert-summary-row>
|
|
<alert-details-table :alert="alert" :loading="loading" />
|
|
</gl-tab>
|
|
<gl-tab :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">
|
|
<system-note v-for="note in alert.notes.nodes" :key="note.id" :note="note" />
|
|
</ul>
|
|
</div>
|
|
</gl-tab>
|
|
</gl-tabs>
|
|
<alert-sidebar
|
|
:alert="alert"
|
|
@toggle-sidebar="toggleSidebar"
|
|
@alert-error="handleAlertSidebarError"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|