alert_widget_form.vue 7.45 KB
Newer Older
1
<script>
2
import _ from 'underscore';
3
import Vue from 'vue';
4
import {
5
6
  GlButton,
  GlButtonGroup,
7
8
9
10
  GlFormGroup,
  GlFormInput,
  GlDropdown,
  GlDropdownItem,
Simon Knox's avatar
Simon Knox committed
11
  GlModal,
12
13
  GlTooltipDirective,
} from '@gitlab/ui';
14
15
16
import { __, s__ } from '~/locale';
import Translate from '~/vue_shared/translate';
import { alertsValidator, queriesValidator } from '../validators';
17
import Icon from '~/vue_shared/components/icon.vue';
18
19
20
21
22
23
24
25
26
27

Vue.use(Translate);

const SUBMIT_ACTION_TEXT = {
  create: __('Add'),
  update: __('Save'),
  delete: __('Delete'),
};

const SUBMIT_BUTTON_CLASS = {
28
29
  create: 'btn-success',
  update: 'btn-success',
30
31
32
33
34
  delete: 'btn-remove',
};

const OPERATORS = {
  greaterThan: '>',
35
  equalTo: '==',
36
37
38
39
  lessThan: '<',
};

export default {
40
  components: {
41
42
    GlButton,
    GlButtonGroup,
43
44
    GlFormGroup,
    GlFormInput,
45
46
    GlDropdown,
    GlDropdownItem,
Simon Knox's avatar
Simon Knox committed
47
    GlModal,
48
49
50
51
    Icon,
  },
  directives: {
    GlTooltipDirective,
52
  },
53
54
55
56
57
  props: {
    disabled: {
      type: Boolean,
      required: true,
    },
Simon Knox's avatar
Simon Knox committed
58
59
60
61
62
    errorMessage: {
      type: String,
      required: false,
      default: '',
    },
63
    alertsToManage: {
64
65
66
      type: Object,
      required: false,
      default: () => ({}),
67
68
69
70
71
72
      validator: alertsValidator,
    },
    relevantQueries: {
      type: Array,
      required: true,
      validator: queriesValidator,
73
    },
Simon Knox's avatar
Simon Knox committed
74
75
76
77
    modalId: {
      type: String,
      required: true,
    },
78
79
80
81
  },
  data() {
    return {
      operators: OPERATORS,
82
83
84
85
      operator: null,
      threshold: null,
      prometheusMetricId: null,
      selectedAlert: {},
86
      alertQuery: '',
87
88
89
    };
  },
  computed: {
90
91
92
93
    isValidQuery() {
      // TODO: Add query validation check (most likely via http request)
      return this.alertQuery.length ? true : null;
    },
94
95
96
97
98
99
    currentQuery() {
      return this.relevantQueries.find(query => query.metricId === this.prometheusMetricId) || {};
    },
    formDisabled() {
      // We need a prometheusMetricId to determine whether we're
      // creating/updating/deleting
100
101
102
103
      return this.disabled || !(this.prometheusMetricId || this.isValidQuery);
    },
    supportsComputedAlerts() {
      return gon.features && gon.features.prometheusComputedAlerts;
104
105
106
107
    },
    queryDropdownLabel() {
      return this.currentQuery.label || s__('PrometheusAlerts|Select query');
    },
108
109
110
111
    haveValuesChanged() {
      return (
        this.operator &&
        this.threshold === Number(this.threshold) &&
112
113
        (this.operator !== this.selectedAlert.operator ||
          this.threshold !== this.selectedAlert.threshold)
114
115
116
      );
    },
    submitAction() {
117
      if (_.isEmpty(this.selectedAlert)) return 'create';
118
119
120
121
122
123
124
125
126
127
128
129
      if (this.haveValuesChanged) return 'update';
      return 'delete';
    },
    submitActionText() {
      return SUBMIT_ACTION_TEXT[this.submitAction];
    },
    submitButtonClass() {
      return SUBMIT_BUTTON_CLASS[this.submitAction];
    },
    isSubmitDisabled() {
      return this.disabled || (this.submitAction === 'create' && !this.haveValuesChanged);
    },
Simon Knox's avatar
Simon Knox committed
130
131
132
133
134
    dropdownTitle() {
      return this.submitAction === 'create'
        ? s__('PrometheusAlerts|Add alert')
        : s__('PrometheusAlerts|Edit alert');
    },
135
136
  },
  watch: {
137
    alertsToManage() {
138
139
      this.resetAlertData();
    },
140
141
142
    submitAction() {
      this.$emit('setAction', this.submitAction);
    },
143
144
  },
  methods: {
145
146
147
148
149
150
151
152
153
154
    selectQuery(queryId) {
      const existingAlertPath = _.findKey(this.alertsToManage, alert => alert.metricId === queryId);
      const existingAlert = this.alertsToManage[existingAlertPath];

      if (existingAlert) {
        this.selectedAlert = existingAlert;
        this.operator = existingAlert.operator;
        this.threshold = existingAlert.threshold;
      } else {
        this.selectedAlert = {};
Miguel Rincon's avatar
Miguel Rincon committed
155
        this.operator = this.operators.greaterThan;
156
157
158
159
160
        this.threshold = null;
      }

      this.prometheusMetricId = queryId;
    },
Miguel Rincon's avatar
Miguel Rincon committed
161
    handleHidden() {
162
163
164
      this.resetAlertData();
      this.$emit('cancel');
    },
Miguel Rincon's avatar
Miguel Rincon committed
165
166
    handleSubmit(e) {
      e.preventDefault();
167
      this.$emit(this.submitAction, {
168
        alert: this.selectedAlert.alert_path,
169
170
        operator: this.operator,
        threshold: this.threshold,
171
        prometheus_metric_id: this.prometheusMetricId,
172
173
174
      });
    },
    resetAlertData() {
175
176
177
178
      this.operator = null;
      this.threshold = null;
      this.prometheusMetricId = null;
      this.selectedAlert = {};
179
180
    },
  },
181
182
183
184
185
186
187
188
  alertQueryText: {
    label: __('Query'),
    validFeedback: __('Query is valid'),
    invalidFeedback: __('Invalid query'),
    descriptionTooltip: __(
      'Example: Usage = single query. (Requested) / (Capacity) = multiple queries combined into a formula.',
    ),
  },
189
190
191
192
};
</script>

<template>
Simon Knox's avatar
Simon Knox committed
193
194
195
196
197
198
199
200
  <gl-modal
    ref="alertModal"
    :title="dropdownTitle"
    :modal-id="modalId"
    :ok-variant="submitAction === 'delete' ? 'danger' : 'success'"
    :ok-title="submitActionText"
    :ok-disabled="formDisabled"
    @ok="handleSubmit"
Miguel Rincon's avatar
Miguel Rincon committed
201
    @hidden="handleHidden"
Simon Knox's avatar
Simon Knox committed
202
  >
Miguel Rincon's avatar
Miguel Rincon committed
203
    <div v-if="errorMessage" class="alert-modal-message danger_message">{{ errorMessage }}</div>
Simon Knox's avatar
Simon Knox committed
204
205
206
207
    <div class="alert-form">
      <gl-form-group
        v-if="supportsComputedAlerts"
        :label="$options.alertQueryText.label"
Miguel Rincon's avatar
Miguel Rincon committed
208
        label-for="alert-query-input"
Simon Knox's avatar
Simon Knox committed
209
210
211
        :valid-feedback="$options.alertQueryText.validFeedback"
        :invalid-feedback="$options.alertQueryText.invalidFeedback"
        :state="isValidQuery"
212
      >
Miguel Rincon's avatar
Miguel Rincon committed
213
        <gl-form-input id="alert-query-input" v-model.trim="alertQuery" :state="isValidQuery" />
Simon Knox's avatar
Simon Knox committed
214
215
216
217
218
219
220
221
222
223
224
        <template #description>
          <div class="d-flex align-items-center">
            {{ __('Single or combined queries') }}
            <icon
              v-gl-tooltip-directive="$options.alertQueryText.descriptionTooltip"
              name="question"
              class="prepend-left-4"
            />
          </div>
        </template>
      </gl-form-group>
Miguel Rincon's avatar
Miguel Rincon committed
225
226
227
228
229
      <gl-form-group v-else label-for="alert-query-dropdown" :label="$options.alertQueryText.label">
        <gl-dropdown
          id="alert-query-dropdown"
          :text="queryDropdownLabel"
          toggle-class="dropdown-menu-toggle"
Simon Knox's avatar
Simon Knox committed
230
        >
Miguel Rincon's avatar
Miguel Rincon committed
231
232
233
234
235
236
237
238
239
          <gl-dropdown-item
            v-for="query in relevantQueries"
            :key="query.metricId"
            @click="selectQuery(query.metricId)"
          >
            {{ query.label }}
          </gl-dropdown-item>
        </gl-dropdown>
      </gl-form-group>
240
241
      <gl-button-group class="mb-2" :label="s__('PrometheusAlerts|Operator')">
        <gl-button
Simon Knox's avatar
Simon Knox committed
242
243
244
245
246
247
          :class="{ active: operator === operators.greaterThan }"
          :disabled="formDisabled"
          type="button"
          @click="operator = operators.greaterThan"
        >
          {{ operators.greaterThan }}
248
249
        </gl-button>
        <gl-button
Simon Knox's avatar
Simon Knox committed
250
251
252
253
254
255
          :class="{ active: operator === operators.equalTo }"
          :disabled="formDisabled"
          type="button"
          @click="operator = operators.equalTo"
        >
          {{ operators.equalTo }}
256
257
        </gl-button>
        <gl-button
Simon Knox's avatar
Simon Knox committed
258
259
260
261
262
263
          :class="{ active: operator === operators.lessThan }"
          :disabled="formDisabled"
          type="button"
          @click="operator = operators.lessThan"
        >
          {{ operators.lessThan }}
264
265
266
267
268
        </gl-button>
      </gl-button-group>
      <gl-form-group :label="s__('PrometheusAlerts|Threshold')" label-for="alerts-threshold">
        <gl-form-input
          id="alerts-threshold"
Simon Knox's avatar
Simon Knox committed
269
270
271
272
          v-model.number="threshold"
          :disabled="formDisabled"
          type="number"
        />
273
      </gl-form-group>
274
    </div>
Simon Knox's avatar
Simon Knox committed
275
  </gl-modal>
276
</template>