summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/alert_management
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-20 14:34:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-20 14:34:42 +0000
commit9f46488805e86b1bc341ea1620b866016c2ce5ed (patch)
treef9748c7e287041e37d6da49e0a29c9511dc34768 /app/assets/javascripts/alert_management
parentdfc92d081ea0332d69c8aca2f0e745cb48ae5e6d (diff)
downloadgitlab-ce-9f46488805e86b1bc341ea1620b866016c2ce5ed.tar.gz
Add latest changes from gitlab-org/gitlab@13-0-stable-ee
Diffstat (limited to 'app/assets/javascripts/alert_management')
-rw-r--r--app/assets/javascripts/alert_management/components/alert_details.vue236
-rw-r--r--app/assets/javascripts/alert_management/components/alert_management_list.vue303
-rw-r--r--app/assets/javascripts/alert_management/constants.js46
-rw-r--r--app/assets/javascripts/alert_management/details.js47
-rw-r--r--app/assets/javascripts/alert_management/graphql/fragments/detailItem.fragment.graphql11
-rw-r--r--app/assets/javascripts/alert_management/graphql/fragments/listItem.fragment.graphql9
-rw-r--r--app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql9
-rw-r--r--app/assets/javascripts/alert_management/graphql/queries/details.query.graphql11
-rw-r--r--app/assets/javascripts/alert_management/graphql/queries/getAlerts.query.graphql11
-rw-r--r--app/assets/javascripts/alert_management/list.js55
-rw-r--r--app/assets/javascripts/alert_management/services/index.js7
11 files changed, 745 insertions, 0 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue
new file mode 100644
index 00000000000..89db7db77d5
--- /dev/null
+++ b/app/assets/javascripts/alert_management/components/alert_details.vue
@@ -0,0 +1,236 @@
+<script>
+import * as Sentry from '@sentry/browser';
+import {
+ GlAlert,
+ GlIcon,
+ GlLoadingIcon,
+ GlDropdown,
+ GlDropdownItem,
+ GlSprintf,
+ GlTabs,
+ GlTab,
+ GlButton,
+ GlTable,
+} from '@gitlab/ui';
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
+import query from '../graphql/queries/details.query.graphql';
+import { fetchPolicies } from '~/lib/graphql';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { ALERTS_SEVERITY_LABELS } from '../constants';
+import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
+
+export default {
+ statuses: {
+ TRIGGERED: s__('AlertManagement|Triggered'),
+ ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
+ RESOLVED: s__('AlertManagement|Resolved'),
+ },
+ i18n: {
+ errorMsg: s__(
+ 'AlertManagement|There was an error displaying the alert. Please refresh the page to try again.',
+ ),
+ fullAlertDetailsTitle: s__('AlertManagement|Alert details'),
+ overviewTitle: s__('AlertManagement|Overview'),
+ reportedAt: s__('AlertManagement|Reported %{when}'),
+ reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'),
+ },
+ severityLabels: ALERTS_SEVERITY_LABELS,
+ components: {
+ GlAlert,
+ GlIcon,
+ GlLoadingIcon,
+ GlSprintf,
+ GlDropdown,
+ GlDropdownItem,
+ GlTab,
+ GlTabs,
+ GlButton,
+ GlTable,
+ TimeAgoTooltip,
+ },
+ mixins: [glFeatureFlagsMixin()],
+ props: {
+ alertId: {
+ type: String,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ newIssuePath: {
+ type: String,
+ required: true,
+ },
+ },
+ apollo: {
+ alert: {
+ fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
+ query,
+ 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);
+ },
+ },
+ },
+ data() {
+ return { alert: null, errored: false, isErrorDismissed: false };
+ },
+ 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;
+ },
+ },
+ methods: {
+ dismissError() {
+ this.isErrorDismissed = true;
+ },
+ updateAlertStatus(status) {
+ this.$apollo
+ .mutate({
+ mutation: updateAlertStatus,
+ variables: {
+ iid: this.alertId,
+ status: status.toUpperCase(),
+ projectPath: this.projectPath,
+ },
+ })
+ .catch(() => {
+ createFlash(
+ s__(
+ 'AlertManagement|There was an error while updating the status of the alert. Please try again.',
+ ),
+ );
+ });
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-alert v-if="showErrorMsg" variant="danger" @dismiss="dismissError">
+ {{ $options.i18n.errorMsg }}
+ </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">
+ <div
+ class="gl-display-flex gl-justify-content-space-between gl-align-items-baseline gl-px-1 py-3 py-md-4 gl-border-b-1 gl-border-b-gray-200 gl-border-b-solid flex-column flex-sm-row"
+ >
+ <div
+ data-testid="alert-header"
+ class="gl-display-flex gl-align-items-center gl-justify-content-center"
+ >
+ <div
+ class="gl-display-inline-flex gl-align-items-center gl-justify-content-space-between"
+ >
+ <gl-icon
+ class="gl-mr-3 align-middle"
+ :size="12"
+ :name="`severity-${alert.severity.toLowerCase()}`"
+ :class="`icon-${alert.severity.toLowerCase()}`"
+ />
+ <strong>{{ $options.severityLabels[alert.severity] }}</strong>
+ </div>
+ <span class="mx-2">&bull;</span>
+ <gl-sprintf :message="reportedAtMessage">
+ <template #when>
+ <time-ago-tooltip :time="alert.createdAt" class="gl-ml-3" />
+ </template>
+ <template #tool>{{ alert.monitoringTool }}</template>
+ </gl-sprintf>
+ </div>
+ <gl-button
+ v-if="glFeatures.createIssueFromAlertEnabled"
+ class="gl-mt-3 mt-sm-0 align-self-center align-self-sm-baseline alert-details-create-issue-button"
+ data-testid="createIssueBtn"
+ :href="newIssuePath"
+ category="primary"
+ variant="success"
+ >
+ {{ s__('AlertManagement|Create issue') }}
+ </gl-button>
+ </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-dropdown :text="$options.statuses[alert.status]" class="gl-absolute gl-right-0" right>
+ <gl-dropdown-item
+ v-for="(label, field) in $options.statuses"
+ :key="field"
+ data-testid="statusDropdownItem"
+ class="gl-vertical-align-middle"
+ @click="updateAlertStatus(label)"
+ >
+ <span class="d-flex">
+ <gl-icon
+ class="flex-shrink-0 append-right-4"
+ :class="{ invisible: label.toUpperCase() !== alert.status }"
+ name="mobile-issue-close"
+ />
+ {{ label }}
+ </span>
+ </gl-dropdown-item>
+ </gl-dropdown>
+ <gl-tabs v-if="alert" data-testid="alertDetailsTabs">
+ <gl-tab data-testid="overviewTab" :title="$options.i18n.overviewTitle">
+ <ul class="pl-4 mb-n1">
+ <li v-if="alert.startedAt" class="my-2">
+ <strong class="bold">{{ s__('AlertManagement|Start time') }}:</strong>
+ <time-ago-tooltip data-testid="startTimeItem" :time="alert.startedAt" />
+ </li>
+ <li v-if="alert.eventCount" class="my-2">
+ <strong class="bold">{{ s__('AlertManagement|Events') }}:</strong>
+ <span data-testid="eventCount">{{ alert.eventCount }}</span>
+ </li>
+ <li v-if="alert.monitoringTool" class="my-2">
+ <strong class="bold">{{ s__('AlertManagement|Tool') }}:</strong>
+ <span data-testid="monitoringTool">{{ alert.monitoringTool }}</span>
+ </li>
+ <li v-if="alert.service" class="my-2">
+ <strong class="bold">{{ s__('AlertManagement|Service') }}:</strong>
+ <span data-testid="service">{{ alert.service }}</span>
+ </li>
+ </ul>
+ </gl-tab>
+ <gl-tab data-testid="fullDetailsTab" :title="$options.i18n.fullAlertDetailsTitle">
+ <gl-table
+ class="alert-management-details-table"
+ :items="[{ key: 'Value', ...alert }]"
+ :show-empty="true"
+ :busy="loading"
+ stacked
+ >
+ <template #empty>
+ {{ s__('AlertManagement|No alert data to display.') }}
+ </template>
+ <template #table-busy>
+ <gl-loading-icon size="lg" color="dark" class="mt-3" />
+ </template>
+ </gl-table>
+ </gl-tab>
+ </gl-tabs>
+ </div>
+ </div>
+</template>
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>
diff --git a/app/assets/javascripts/alert_management/constants.js b/app/assets/javascripts/alert_management/constants.js
new file mode 100644
index 00000000000..9df01d9d0b5
--- /dev/null
+++ b/app/assets/javascripts/alert_management/constants.js
@@ -0,0 +1,46 @@
+import { s__ } from '~/locale';
+
+export const ALERTS_SEVERITY_LABELS = {
+ CRITICAL: s__('AlertManagement|Critical'),
+ HIGH: s__('AlertManagement|High'),
+ MEDIUM: s__('AlertManagement|Medium'),
+ LOW: s__('AlertManagement|Low'),
+ INFO: s__('AlertManagement|Info'),
+ UNKNOWN: s__('AlertManagement|Unknown'),
+};
+
+export const ALERTS_STATUS = {
+ OPEN: 'OPEN',
+ TRIGGERED: 'TRIGGERED',
+ ACKNOWLEDGED: 'ACKNOWLEDGED',
+ RESOLVED: 'RESOLVED',
+ ALL: 'ALL',
+};
+
+export const ALERTS_STATUS_TABS = [
+ {
+ title: s__('AlertManagement|Open'),
+ status: ALERTS_STATUS.OPEN,
+ filters: [ALERTS_STATUS.TRIGGERED, ALERTS_STATUS.ACKNOWLEDGED],
+ },
+ {
+ title: s__('AlertManagement|Triggered'),
+ status: ALERTS_STATUS.TRIGGERED,
+ filters: [ALERTS_STATUS.TRIGGERED],
+ },
+ {
+ title: s__('AlertManagement|Acknowledged'),
+ status: ALERTS_STATUS.ACKNOWLEDGED,
+ filters: [ALERTS_STATUS.ACKNOWLEDGED],
+ },
+ {
+ title: s__('AlertManagement|Resolved'),
+ status: ALERTS_STATUS.RESOLVED,
+ filters: [ALERTS_STATUS.RESOLVED],
+ },
+ {
+ title: s__('AlertManagement|All alerts'),
+ status: ALERTS_STATUS.ALL,
+ filters: [ALERTS_STATUS.TRIGGERED, ALERTS_STATUS.ACKNOWLEDGED, ALERTS_STATUS.RESOLVED],
+ },
+];
diff --git a/app/assets/javascripts/alert_management/details.js b/app/assets/javascripts/alert_management/details.js
new file mode 100644
index 00000000000..d3523e0a29d
--- /dev/null
+++ b/app/assets/javascripts/alert_management/details.js
@@ -0,0 +1,47 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
+import AlertDetails from './components/alert_details.vue';
+
+Vue.use(VueApollo);
+
+export default selector => {
+ const domEl = document.querySelector(selector);
+ const { alertId, projectPath, newIssuePath } = domEl.dataset;
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(
+ {},
+ {
+ cacheConfig: {
+ dataIdFromObject: object => {
+ // eslint-disable-next-line no-underscore-dangle
+ if (object.__typename === 'AlertManagementAlert') {
+ return object.iid;
+ }
+ return defaultDataIdFromObject(object);
+ },
+ },
+ },
+ ),
+ });
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: selector,
+ apolloProvider,
+ components: {
+ AlertDetails,
+ },
+ render(createElement) {
+ return createElement('alert-details', {
+ props: {
+ alertId,
+ projectPath,
+ newIssuePath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/alert_management/graphql/fragments/detailItem.fragment.graphql b/app/assets/javascripts/alert_management/graphql/fragments/detailItem.fragment.graphql
new file mode 100644
index 00000000000..df802616e97
--- /dev/null
+++ b/app/assets/javascripts/alert_management/graphql/fragments/detailItem.fragment.graphql
@@ -0,0 +1,11 @@
+#import "./listItem.fragment.graphql"
+
+fragment AlertDetailItem on AlertManagementAlert {
+ ...AlertListItem
+ createdAt
+ monitoringTool
+ service
+ description
+ updatedAt
+ details
+}
diff --git a/app/assets/javascripts/alert_management/graphql/fragments/listItem.fragment.graphql b/app/assets/javascripts/alert_management/graphql/fragments/listItem.fragment.graphql
new file mode 100644
index 00000000000..fffe07b0cfd
--- /dev/null
+++ b/app/assets/javascripts/alert_management/graphql/fragments/listItem.fragment.graphql
@@ -0,0 +1,9 @@
+fragment AlertListItem on AlertManagementAlert {
+ iid
+ title
+ severity
+ status
+ startedAt
+ endedAt
+ eventCount
+}
diff --git a/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql b/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql
new file mode 100644
index 00000000000..009ae0b2930
--- /dev/null
+++ b/app/assets/javascripts/alert_management/graphql/mutations/update_alert_status.graphql
@@ -0,0 +1,9 @@
+mutation ($projectPath: ID!, $status: AlertManagementStatus!, $iid: String!) {
+ updateAlertStatus(input: { iid: $iid, status: $status, projectPath: $projectPath }) {
+ errors
+ alert {
+ iid,
+ status,
+ }
+ }
+}
diff --git a/app/assets/javascripts/alert_management/graphql/queries/details.query.graphql b/app/assets/javascripts/alert_management/graphql/queries/details.query.graphql
new file mode 100644
index 00000000000..7c77715fad2
--- /dev/null
+++ b/app/assets/javascripts/alert_management/graphql/queries/details.query.graphql
@@ -0,0 +1,11 @@
+#import "../fragments/detailItem.fragment.graphql"
+
+query alertDetails($fullPath: ID!, $alertId: String) {
+ project(fullPath: $fullPath) {
+ alertManagementAlerts(iid: $alertId) {
+ nodes {
+ ...AlertDetailItem
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/alert_management/graphql/queries/getAlerts.query.graphql b/app/assets/javascripts/alert_management/graphql/queries/getAlerts.query.graphql
new file mode 100644
index 00000000000..54b66389d5b
--- /dev/null
+++ b/app/assets/javascripts/alert_management/graphql/queries/getAlerts.query.graphql
@@ -0,0 +1,11 @@
+#import "../fragments/listItem.fragment.graphql"
+
+query getAlerts($projectPath: ID!, $statuses: [AlertManagementStatus!]) {
+ project(fullPath: $projectPath) {
+ alertManagementAlerts(statuses: $statuses) {
+ nodes {
+ ...AlertListItem
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/alert_management/list.js b/app/assets/javascripts/alert_management/list.js
new file mode 100644
index 00000000000..cae6a536b56
--- /dev/null
+++ b/app/assets/javascripts/alert_management/list.js
@@ -0,0 +1,55 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import AlertManagementList from './components/alert_management_list.vue';
+
+Vue.use(VueApollo);
+
+export default () => {
+ const selector = '#js-alert_management';
+
+ const domEl = document.querySelector(selector);
+ const { projectPath, enableAlertManagementPath, emptyAlertSvgPath } = domEl.dataset;
+ let { alertManagementEnabled, userCanEnableAlertManagement } = domEl.dataset;
+
+ alertManagementEnabled = parseBoolean(alertManagementEnabled);
+ userCanEnableAlertManagement = parseBoolean(userCanEnableAlertManagement);
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(
+ {},
+ {
+ cacheConfig: {
+ dataIdFromObject: object => {
+ // eslint-disable-next-line no-underscore-dangle
+ if (object.__typename === 'AlertManagementAlert') {
+ return object.iid;
+ }
+ return defaultDataIdFromObject(object);
+ },
+ },
+ },
+ ),
+ });
+
+ return new Vue({
+ el: selector,
+ apolloProvider,
+ components: {
+ AlertManagementList,
+ },
+ render(createElement) {
+ return createElement('alert-management-list', {
+ props: {
+ projectPath,
+ enableAlertManagementPath,
+ emptyAlertSvgPath,
+ alertManagementEnabled,
+ userCanEnableAlertManagement,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/alert_management/services/index.js b/app/assets/javascripts/alert_management/services/index.js
new file mode 100644
index 00000000000..787603d3e7a
--- /dev/null
+++ b/app/assets/javascripts/alert_management/services/index.js
@@ -0,0 +1,7 @@
+import axios from '~/lib/utils/axios_utils';
+
+export default {
+ getAlertManagementList({ endpoint }) {
+ return axios.get(endpoint);
+ },
+};