Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/monitoring/components/alert_widget.vue')
-rw-r--r--app/assets/javascripts/monitoring/components/alert_widget.vue286
1 files changed, 286 insertions, 0 deletions
diff --git a/app/assets/javascripts/monitoring/components/alert_widget.vue b/app/assets/javascripts/monitoring/components/alert_widget.vue
new file mode 100644
index 00000000000..86a793c854e
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/alert_widget.vue
@@ -0,0 +1,286 @@
+<script>
+import { GlBadge, GlLoadingIcon, GlModalDirective, GlIcon, GlTooltip, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import createFlash from '~/flash';
+import AlertWidgetForm from './alert_widget_form.vue';
+import AlertsService from '../services/alerts_service';
+import { alertsValidator, queriesValidator } from '../validators';
+import { OPERATORS } from '../constants';
+import { values, get } from 'lodash';
+
+export default {
+ components: {
+ AlertWidgetForm,
+ GlBadge,
+ GlLoadingIcon,
+ GlIcon,
+ GlTooltip,
+ GlSprintf,
+ },
+ directives: {
+ GlModal: GlModalDirective,
+ },
+ props: {
+ alertsEndpoint: {
+ type: String,
+ required: true,
+ },
+ showLoadingState: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ // { [alertPath]: { alert_attributes } }. Populated from subsequent API calls.
+ // Includes only the metrics/alerts to be managed by this widget.
+ alertsToManage: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ validator: alertsValidator,
+ },
+ // [{ metric+query_attributes }]. Represents queries (and alerts) we know about
+ // on intial fetch. Essentially used for reference.
+ relevantQueries: {
+ type: Array,
+ required: true,
+ validator: queriesValidator,
+ },
+ modalId: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ service: null,
+ errorMessage: null,
+ isLoading: false,
+ apiAction: 'create',
+ };
+ },
+ i18n: {
+ alertsCountMsg: s__('PrometheusAlerts|%{count} alerts applied'),
+ singleFiringMsg: s__('PrometheusAlerts|Firing: %{alert}'),
+ multipleFiringMsg: s__('PrometheusAlerts|%{firingCount} firing'),
+ firingAlertsTooltip: s__('PrometheusAlerts|Firing: %{alerts}'),
+ },
+ computed: {
+ singleAlertSummary() {
+ return {
+ message: this.isFiring ? this.$options.i18n.singleFiringMsg : this.thresholds[0],
+ alert: this.thresholds[0],
+ };
+ },
+ multipleAlertsSummary() {
+ return {
+ message: this.isFiring
+ ? `${this.$options.i18n.alertsCountMsg}, ${this.$options.i18n.multipleFiringMsg}`
+ : this.$options.i18n.alertsCountMsg,
+ count: this.thresholds.length,
+ firingCount: this.firingAlerts.length,
+ };
+ },
+ shouldShowLoadingIcon() {
+ return this.showLoadingState && this.isLoading;
+ },
+ thresholds() {
+ const alertsToManage = Object.keys(this.alertsToManage);
+ return alertsToManage.map(this.formatAlertSummary);
+ },
+ hasAlerts() {
+ return Boolean(Object.keys(this.alertsToManage).length);
+ },
+ hasMultipleAlerts() {
+ return this.thresholds.length > 1;
+ },
+ isFiring() {
+ return Boolean(this.firingAlerts.length);
+ },
+ firingAlerts() {
+ return values(this.alertsToManage).filter(alert =>
+ this.passedAlertThreshold(this.getQueryData(alert), alert),
+ );
+ },
+ formattedFiringAlerts() {
+ return this.firingAlerts.map(alert => this.formatAlertSummary(alert.alert_path));
+ },
+ configuredAlert() {
+ return this.hasAlerts ? values(this.alertsToManage)[0].metricId : '';
+ },
+ },
+ created() {
+ this.service = new AlertsService({ alertsEndpoint: this.alertsEndpoint });
+ this.fetchAlertData();
+ },
+ methods: {
+ fetchAlertData() {
+ this.isLoading = true;
+
+ const queriesWithAlerts = this.relevantQueries.filter(query => query.alert_path);
+
+ return Promise.all(
+ queriesWithAlerts.map(query =>
+ this.service
+ .readAlert(query.alert_path)
+ .then(alertAttributes => this.setAlert(alertAttributes, query.metricId)),
+ ),
+ )
+ .then(() => {
+ this.isLoading = false;
+ })
+ .catch(() => {
+ createFlash(s__('PrometheusAlerts|Error fetching alert'));
+ this.isLoading = false;
+ });
+ },
+ setAlert(alertAttributes, metricId) {
+ this.$emit('setAlerts', alertAttributes.alert_path, { ...alertAttributes, metricId });
+ },
+ removeAlert(alertPath) {
+ this.$emit('setAlerts', alertPath, null);
+ },
+ formatAlertSummary(alertPath) {
+ const alert = this.alertsToManage[alertPath];
+ const alertQuery = this.relevantQueries.find(query => query.metricId === alert.metricId);
+
+ return `${alertQuery.label} ${alert.operator} ${alert.threshold}`;
+ },
+ passedAlertThreshold(data, alert) {
+ const { threshold, operator } = alert;
+
+ switch (operator) {
+ case OPERATORS.greaterThan:
+ return data.some(value => value > threshold);
+ case OPERATORS.lessThan:
+ return data.some(value => value < threshold);
+ case OPERATORS.equalTo:
+ return data.some(value => value === threshold);
+ default:
+ return false;
+ }
+ },
+ getQueryData(alert) {
+ const alertQuery = this.relevantQueries.find(query => query.metricId === alert.metricId);
+
+ return get(alertQuery, 'result[0].values', []).map(value => get(value, '[1]', null));
+ },
+ showModal() {
+ this.$root.$emit('bv::show::modal', this.modalId);
+ },
+ hideModal() {
+ this.errorMessage = null;
+ this.$root.$emit('bv::hide::modal', this.modalId);
+ },
+ handleSetApiAction(apiAction) {
+ this.apiAction = apiAction;
+ },
+ handleCreate({ operator, threshold, prometheus_metric_id }) {
+ const newAlert = { operator, threshold, prometheus_metric_id };
+ this.isLoading = true;
+ this.service
+ .createAlert(newAlert)
+ .then(alertAttributes => {
+ this.setAlert(alertAttributes, prometheus_metric_id);
+ this.isLoading = false;
+ this.hideModal();
+ })
+ .catch(() => {
+ this.errorMessage = s__('PrometheusAlerts|Error creating alert');
+ this.isLoading = false;
+ });
+ },
+ handleUpdate({ alert, operator, threshold }) {
+ const updatedAlert = { operator, threshold };
+ this.isLoading = true;
+ this.service
+ .updateAlert(alert, updatedAlert)
+ .then(alertAttributes => {
+ this.setAlert(alertAttributes, this.alertsToManage[alert].metricId);
+ this.isLoading = false;
+ this.hideModal();
+ })
+ .catch(() => {
+ this.errorMessage = s__('PrometheusAlerts|Error saving alert');
+ this.isLoading = false;
+ });
+ },
+ handleDelete({ alert }) {
+ this.isLoading = true;
+ this.service
+ .deleteAlert(alert)
+ .then(() => {
+ this.removeAlert(alert);
+ this.isLoading = false;
+ this.hideModal();
+ })
+ .catch(() => {
+ this.errorMessage = s__('PrometheusAlerts|Error deleting alert');
+ this.isLoading = false;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="prometheus-alert-widget dropdown flex-grow-2 overflow-hidden">
+ <gl-loading-icon v-if="shouldShowLoadingIcon" :inline="true" />
+ <span v-else-if="errorMessage" ref="alertErrorMessage" class="alert-error-message">{{
+ errorMessage
+ }}</span>
+ <span
+ v-else-if="hasAlerts"
+ ref="alertCurrentSetting"
+ class="alert-current-setting cursor-pointer d-flex"
+ @click="showModal"
+ >
+ <gl-badge
+ :variant="isFiring ? 'danger' : 'secondary'"
+ pill
+ class="d-flex-center text-truncate"
+ >
+ <gl-icon name="warning" :size="16" class="flex-shrink-0" />
+ <span class="text-truncate gl-pl-1-deprecated-no-really-do-not-use-me">
+ <gl-sprintf
+ :message="
+ hasMultipleAlerts ? multipleAlertsSummary.message : singleAlertSummary.message
+ "
+ >
+ <template #alert>
+ {{ singleAlertSummary.alert }}
+ </template>
+ <template #count>
+ {{ multipleAlertsSummary.count }}
+ </template>
+ <template #firingCount>
+ {{ multipleAlertsSummary.firingCount }}
+ </template>
+ </gl-sprintf>
+ </span>
+ </gl-badge>
+ <gl-tooltip v-if="hasMultipleAlerts && isFiring" :target="() => $refs.alertCurrentSetting">
+ <gl-sprintf :message="$options.i18n.firingAlertsTooltip">
+ <template #alerts>
+ <div v-for="alert in formattedFiringAlerts" :key="alert.alert_path">
+ {{ alert }}
+ </div>
+ </template>
+ </gl-sprintf>
+ </gl-tooltip>
+ </span>
+ <alert-widget-form
+ ref="widgetForm"
+ :disabled="isLoading"
+ :alerts-to-manage="alertsToManage"
+ :relevant-queries="relevantQueries"
+ :error-message="errorMessage"
+ :configured-alert="configuredAlert"
+ :modal-id="modalId"
+ @create="handleCreate"
+ @update="handleUpdate"
+ @delete="handleDelete"
+ @cancel="hideModal"
+ @setAction="handleSetApiAction"
+ />
+ </div>
+</template>