summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/alert_management/components/alert_management_list.vue
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/alert_management/components/alert_management_list.vue')
-rw-r--r--app/assets/javascripts/alert_management/components/alert_management_list.vue303
1 files changed, 303 insertions, 0 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_management_list.vue b/app/assets/javascripts/alert_management/components/alert_management_list.vue
new file mode 100644
index 00000000000..74fc19ff3d4
--- /dev/null
+++ b/app/assets/javascripts/alert_management/components/alert_management_list.vue
@@ -0,0 +1,303 @@
+<script>
+import {
+ GlEmptyState,
+ GlDeprecatedButton,
+ GlLoadingIcon,
+ GlTable,
+ GlAlert,
+ GlIcon,
+ GlDropdown,
+ GlDropdownItem,
+ GlTabs,
+ GlTab,
+} from '@gitlab/ui';
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
+import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import getAlerts from '../graphql/queries/getAlerts.query.graphql';
+import { ALERTS_STATUS, ALERTS_STATUS_TABS, ALERTS_SEVERITY_LABELS } from '../constants';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
+import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
+
+const tdClass = 'table-col d-flex d-md-table-cell align-items-center';
+const bodyTrClass =
+ 'gl-border-1 gl-border-t-solid gl-border-gray-100 hover-bg-blue-50 hover-gl-cursor-pointer hover-gl-border-b-solid hover-gl-border-blue-200';
+
+export default {
+ bodyTrClass,
+ i18n: {
+ noAlertsMsg: s__(
+ "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page.",
+ ),
+ errorMsg: s__(
+ "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear.",
+ ),
+ },
+ fields: [
+ {
+ key: 'severity',
+ label: s__('AlertManagement|Severity'),
+ tdClass: `${tdClass} rounded-top text-capitalize`,
+ },
+ {
+ key: 'startedAt',
+ label: s__('AlertManagement|Start time'),
+ tdClass,
+ },
+ {
+ key: 'endedAt',
+ label: s__('AlertManagement|End time'),
+ tdClass,
+ },
+ {
+ key: 'title',
+ label: s__('AlertManagement|Alert'),
+ thClass: 'w-30p',
+ tdClass,
+ },
+ {
+ key: 'eventCount',
+ label: s__('AlertManagement|Events'),
+ thClass: 'text-right event-count',
+ tdClass: `${tdClass} text-md-right event-count`,
+ },
+ {
+ key: 'status',
+ thClass: 'w-15p',
+ label: s__('AlertManagement|Status'),
+ tdClass: `${tdClass} rounded-bottom`,
+ },
+ ],
+ statuses: {
+ [ALERTS_STATUS.TRIGGERED]: s__('AlertManagement|Triggered'),
+ [ALERTS_STATUS.ACKNOWLEDGED]: s__('AlertManagement|Acknowledged'),
+ [ALERTS_STATUS.RESOLVED]: s__('AlertManagement|Resolved'),
+ },
+ severityLabels: ALERTS_SEVERITY_LABELS,
+ statusTabs: ALERTS_STATUS_TABS,
+ components: {
+ GlEmptyState,
+ GlLoadingIcon,
+ GlTable,
+ GlAlert,
+ GlDeprecatedButton,
+ TimeAgo,
+ GlDropdown,
+ GlDropdownItem,
+ GlIcon,
+ GlTabs,
+ GlTab,
+ },
+ mixins: [glFeatureFlagsMixin()],
+ props: {
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ alertManagementEnabled: {
+ type: Boolean,
+ required: true,
+ },
+ enableAlertManagementPath: {
+ type: String,
+ required: true,
+ },
+ userCanEnableAlertManagement: {
+ type: Boolean,
+ required: true,
+ },
+ emptyAlertSvgPath: {
+ type: String,
+ required: true,
+ },
+ },
+ apollo: {
+ alerts: {
+ query: getAlerts,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ statuses: this.statusFilter,
+ };
+ },
+ update(data) {
+ return data.project.alertManagementAlerts.nodes;
+ },
+ error() {
+ this.errored = true;
+ },
+ },
+ },
+ data() {
+ return {
+ alerts: null,
+ errored: false,
+ isAlertDismissed: false,
+ isErrorAlertDismissed: false,
+ statusFilter: this.$options.statusTabs[4].filters,
+ };
+ },
+ computed: {
+ showNoAlertsMsg() {
+ return !this.errored && !this.loading && !this.alerts?.length && !this.isAlertDismissed;
+ },
+ showErrorMsg() {
+ return this.errored && !this.isErrorAlertDismissed;
+ },
+ loading() {
+ return this.$apollo.queries.alerts.loading;
+ },
+ },
+ methods: {
+ filterAlertsByStatus(tabIndex) {
+ this.statusFilter = this.$options.statusTabs[tabIndex].filters;
+ },
+ capitalizeFirstCharacter,
+ updateAlertStatus(status, iid) {
+ this.$apollo
+ .mutate({
+ mutation: updateAlertStatus,
+ variables: {
+ iid,
+ status: status.toUpperCase(),
+ projectPath: this.projectPath,
+ },
+ })
+ .then(() => {
+ this.$apollo.queries.alerts.refetch();
+ })
+ .catch(() => {
+ createFlash(
+ s__(
+ 'AlertManagement|There was an error while updating the status of the alert. Please try again.',
+ ),
+ );
+ });
+ },
+ navigateToAlertDetails({ iid }) {
+ return visitUrl(joinPaths(window.location.pathname, iid, 'details'));
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <div v-if="alertManagementEnabled" class="alert-management-list">
+ <gl-alert v-if="showNoAlertsMsg" @dismiss="isAlertDismissed = true">
+ {{ $options.i18n.noAlertsMsg }}
+ </gl-alert>
+ <gl-alert v-if="showErrorMsg" variant="danger" @dismiss="isErrorAlertDismissed = true">
+ {{ $options.i18n.errorMsg }}
+ </gl-alert>
+
+ <gl-tabs v-if="glFeatures.alertListStatusFilteringEnabled" @input="filterAlertsByStatus">
+ <gl-tab v-for="tab in $options.statusTabs" :key="tab.status">
+ <template slot="title">
+ <span>{{ tab.title }}</span>
+ </template>
+ </gl-tab>
+ </gl-tabs>
+
+ <h4 class="d-block d-md-none my-3">
+ {{ s__('AlertManagement|Alerts') }}
+ </h4>
+ <gl-table
+ class="alert-management-table mt-3"
+ :items="alerts"
+ :fields="$options.fields"
+ :show-empty="true"
+ :busy="loading"
+ stacked="md"
+ :tbody-tr-class="$options.bodyTrClass"
+ @row-clicked="navigateToAlertDetails"
+ >
+ <template #cell(severity)="{ item }">
+ <div
+ class="d-inline-flex align-items-center justify-content-between"
+ data-testid="severityField"
+ >
+ <gl-icon
+ class="mr-2"
+ :size="12"
+ :name="`severity-${item.severity.toLowerCase()}`"
+ :class="`icon-${item.severity.toLowerCase()}`"
+ />
+ {{ $options.severityLabels[item.severity] }}
+ </div>
+ </template>
+
+ <template #cell(startedAt)="{ item }">
+ <time-ago v-if="item.startedAt" :time="item.startedAt" />
+ </template>
+
+ <template #cell(endedAt)="{ item }">
+ <time-ago v-if="item.endedAt" :time="item.endedAt" />
+ </template>
+
+ <template #cell(title)="{ item }">
+ <div class="gl-max-w-full text-truncate">{{ item.title }}</div>
+ </template>
+
+ <template #cell(status)="{ item }">
+ <gl-dropdown
+ :text="capitalizeFirstCharacter(item.status.toLowerCase())"
+ class="w-100"
+ right
+ >
+ <gl-dropdown-item
+ v-for="(label, field) in $options.statuses"
+ :key="field"
+ @click="updateAlertStatus(label, item.iid)"
+ >
+ <span class="d-flex">
+ <gl-icon
+ class="flex-shrink-0 append-right-4"
+ :class="{ invisible: label.toUpperCase() !== item.status }"
+ name="mobile-issue-close"
+ />
+ {{ label }}
+ </span>
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </template>
+
+ <template #empty>
+ {{ s__('AlertManagement|No alerts to display.') }}
+ </template>
+
+ <template #table-busy>
+ <gl-loading-icon size="lg" color="dark" class="mt-3" />
+ </template>
+ </gl-table>
+ </div>
+ <gl-empty-state
+ v-else
+ :title="s__('AlertManagement|Surface alerts in GitLab')"
+ :svg-path="emptyAlertSvgPath"
+ >
+ <template #description>
+ <div class="d-block">
+ <span>{{
+ s__(
+ 'AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents.',
+ )
+ }}</span>
+ <a href="/help/user/project/operations/alert_management.html" target="_blank">
+ {{ s__('AlertManagement|More information') }}
+ </a>
+ </div>
+ <div v-if="userCanEnableAlertManagement" class="d-block center pt-4">
+ <gl-deprecated-button
+ category="primary"
+ variant="success"
+ :href="enableAlertManagementPath"
+ >
+ {{ s__('AlertManagement|Authorize external service') }}
+ </gl-deprecated-button>
+ </div>
+ </template>
+ </gl-empty-state>
+ </div>
+</template>