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/custom_metrics/components/custom_metrics_form_fields.vue')
-rw-r--r--app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue294
1 files changed, 294 insertions, 0 deletions
diff --git a/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue b/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue
new file mode 100644
index 00000000000..f5207b47f69
--- /dev/null
+++ b/app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue
@@ -0,0 +1,294 @@
+<script>
+import { GlFormInput, GlLink, GlFormGroup, GlFormRadioGroup, GlLoadingIcon } from '@gitlab/ui';
+import { debounce } from 'lodash';
+import { __, s__ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import csrf from '~/lib/utils/csrf';
+import axios from '~/lib/utils/axios_utils';
+import statusCodes from '~/lib/utils/http_status';
+import { backOff } from '~/lib/utils/common_utils';
+import { queryTypes, formDataValidator } from '../constants';
+
+const VALIDATION_REQUEST_TIMEOUT = 10000;
+const axiosCancelToken = axios.CancelToken;
+let cancelTokenSource;
+
+function backOffRequest(makeRequestCallback) {
+ return backOff((next, stop) => {
+ makeRequestCallback()
+ .then(resp => {
+ if (resp.status === statusCodes.OK) {
+ stop(resp);
+ } else {
+ next();
+ }
+ })
+ // If the request is cancelled by axios
+ // then consider it as noop so that its not
+ // caught by subsequent catches
+ .catch(thrown => (axios.isCancel(thrown) ? undefined : stop(thrown)));
+ }, VALIDATION_REQUEST_TIMEOUT);
+}
+
+export default {
+ components: {
+ GlFormInput,
+ GlLink,
+ GlFormGroup,
+ GlFormRadioGroup,
+ GlLoadingIcon,
+ Icon,
+ },
+ props: {
+ formOperation: {
+ type: String,
+ required: true,
+ },
+ formData: {
+ type: Object,
+ required: false,
+ default: () => ({
+ title: '',
+ yLabel: '',
+ query: '',
+ unit: '',
+ group: '',
+ legend: '',
+ }),
+ validator: formDataValidator,
+ },
+ metricPersisted: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ validateQueryPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ const group = this.formData.group.length ? this.formData.group : queryTypes.business;
+
+ return {
+ queryIsValid: null,
+ queryValidateInFlight: false,
+ ...this.formData,
+ group,
+ };
+ },
+ computed: {
+ formIsValid() {
+ return Boolean(
+ this.queryIsValid &&
+ this.title.length &&
+ this.yLabel.length &&
+ this.unit.length &&
+ this.group.length,
+ );
+ },
+ validQueryMsg() {
+ return this.queryIsValid ? s__('Metrics|PromQL query is valid') : '';
+ },
+ invalidQueryMsg() {
+ return !this.queryIsValid ? this.errorMessage : '';
+ },
+ },
+ watch: {
+ formIsValid(value) {
+ this.$emit('formValidation', value);
+ },
+ },
+ beforeMount() {
+ if (this.metricPersisted) {
+ this.validateQuery();
+ }
+ },
+ methods: {
+ requestValidation(query, cancelToken) {
+ return backOffRequest(() =>
+ axios.post(
+ this.validateQueryPath,
+ {
+ query,
+ },
+ {
+ cancelToken,
+ },
+ ),
+ );
+ },
+ setFormState(isValid, inFlight, message) {
+ this.queryIsValid = isValid;
+ this.queryValidateInFlight = inFlight;
+ this.errorMessage = message;
+ },
+ validateQuery() {
+ if (!this.query) {
+ this.setFormState(null, false, '');
+ return;
+ }
+ this.setFormState(null, true, '');
+ // cancel previously dispatched backoff request
+ if (cancelTokenSource) {
+ cancelTokenSource.cancel();
+ }
+ // Creating a new token for each request because
+ // if a single token is used it can cancel existing requests
+ // as well.
+ cancelTokenSource = axiosCancelToken.source();
+ this.requestValidation(this.query, cancelTokenSource.token)
+ .then(res => {
+ const response = res.data;
+ const { valid, error } = response.query;
+ if (response.success) {
+ this.setFormState(valid, false, valid ? '' : error);
+ } else {
+ throw new Error(__('There was an error trying to validate your query'));
+ }
+ })
+ .catch(() => {
+ this.setFormState(
+ false,
+ false,
+ s__('Metrics|There was an error trying to validate your query'),
+ );
+ });
+ },
+ debouncedValidateQuery: debounce(function checkQuery() {
+ this.validateQuery();
+ }, 500),
+ },
+ csrfToken: csrf.token || '',
+ formGroupOptions: [
+ { text: __('Business'), value: queryTypes.business },
+ { text: __('Response'), value: queryTypes.response },
+ { text: __('System'), value: queryTypes.system },
+ ],
+};
+</script>
+
+<template>
+ <div>
+ <input ref="method" type="hidden" name="_method" :value="formOperation" />
+ <input :value="$options.csrfToken" type="hidden" name="authenticity_token" />
+ <gl-form-group :label="__('Name')" label-for="prometheus_metric_title" label-class="label-bold">
+ <gl-form-input
+ id="prometheus_metric_title"
+ v-model="title"
+ name="prometheus_metric[title]"
+ class="form-control"
+ :placeholder="s__('Metrics|e.g. Throughput')"
+ data-qa-selector="custom_metric_prometheus_title_field"
+ required
+ />
+ <span class="form-text text-muted">{{ s__('Metrics|Used as a title for the chart') }}</span>
+ </gl-form-group>
+ <gl-form-group :label="__('Type')" label-for="prometheus_metric_group" label-class="label-bold">
+ <gl-form-radio-group
+ id="metric-group"
+ v-model="group"
+ :options="$options.formGroupOptions"
+ :checked="group"
+ name="prometheus_metric[group]"
+ />
+ <span class="form-text text-muted">{{ s__('Metrics|For grouping similar metrics') }}</span>
+ </gl-form-group>
+ <gl-form-group
+ :label="__('Query')"
+ label-for="prometheus_metric_query"
+ label-class="label-bold"
+ :state="queryIsValid"
+ >
+ <gl-form-input
+ id="prometheus_metric_query"
+ v-model.trim="query"
+ data-qa-selector="custom_metric_prometheus_query_field"
+ name="prometheus_metric[query]"
+ class="form-control"
+ :placeholder="s__('Metrics|e.g. rate(http_requests_total[5m])')"
+ required
+ :state="queryIsValid"
+ @input="debouncedValidateQuery"
+ />
+ <span v-if="queryValidateInFlight" class="form-text text-muted">
+ <gl-loading-icon :inline="true" class="mr-1 align-middle" />
+ {{ s__('Metrics|Validating query') }}
+ </span>
+ <slot v-if="!queryValidateInFlight" name="valid-feedback">
+ <span class="form-text cgreen">
+ {{ validQueryMsg }}
+ </span>
+ </slot>
+ <slot v-if="!queryValidateInFlight" name="invalid-feedback">
+ <span class="form-text cred">
+ {{ invalidQueryMsg }}
+ </span>
+ </slot>
+ <span v-show="query.length === 0" class="form-text text-muted">
+ {{ s__('Metrics|Must be a valid PromQL query.') }}
+ <gl-link href="https://prometheus.io/docs/prometheus/latest/querying/basics/" tabindex="-1">
+ {{ s__('Metrics|Prometheus Query Documentation') }}
+ <icon name="external-link" :size="12" />
+ </gl-link>
+ </span>
+ </gl-form-group>
+ <gl-form-group
+ :label="s__('Metrics|Y-axis label')"
+ label-for="prometheus_metric_y_label"
+ label-class="label-bold"
+ >
+ <gl-form-input
+ id="prometheus_metric_y_label"
+ v-model="yLabel"
+ data-qa-selector="custom_metric_prometheus_y_label_field"
+ name="prometheus_metric[y_label]"
+ class="form-control"
+ :placeholder="s__('Metrics|e.g. Requests/second')"
+ required
+ />
+ <span class="form-text text-muted">
+ {{
+ s__('Metrics|Label of the y-axis (usually the unit). The x-axis always represents time.')
+ }}
+ </span>
+ </gl-form-group>
+ <gl-form-group
+ :label="s__('Metrics|Unit label')"
+ label-for="prometheus_metric_unit"
+ label-class="label-bold"
+ >
+ <gl-form-input
+ id="prometheus_metric_unit"
+ v-model="unit"
+ data-qa-selector="custom_metric_prometheus_unit_label_field"
+ name="prometheus_metric[unit]"
+ class="form-control"
+ :placeholder="s__('Metrics|e.g. req/sec')"
+ required
+ />
+ </gl-form-group>
+ <gl-form-group
+ :label="s__('Metrics|Legend label (optional)')"
+ label-for="prometheus_metric_legend"
+ label-class="label-bold"
+ >
+ <gl-form-input
+ id="prometheus_metric_legend"
+ v-model="legend"
+ data-qa-selector="custom_metric_prometheus_legend_label_field"
+ name="prometheus_metric[legend]"
+ class="form-control"
+ :placeholder="s__('Metrics|e.g. HTTP requests')"
+ required
+ />
+ <span class="form-text text-muted">
+ {{
+ s__(
+ 'Metrics|Used if the query returns a single series. If it returns multiple series, their legend labels will be picked up from the response.',
+ )
+ }}
+ </span>
+ </gl-form-group>
+ </div>
+</template>