diff options
Diffstat (limited to 'app/assets/javascripts/monitoring/components/charts')
5 files changed, 202 insertions, 5 deletions
diff --git a/app/assets/javascripts/monitoring/components/charts/gauge.vue b/app/assets/javascripts/monitoring/components/charts/gauge.vue new file mode 100644 index 00000000000..63fa60bbdf0 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/charts/gauge.vue @@ -0,0 +1,122 @@ +<script> +import { GlResizeObserverDirective } from '@gitlab/ui'; +import { GlGaugeChart } from '@gitlab/ui/dist/charts'; +import { isFinite, isArray, isInteger } from 'lodash'; +import { graphDataValidatorForValues } from '../../utils'; +import { getValidThresholds } from './options'; +import { getFormatter, SUPPORTED_FORMATS } from '~/lib/utils/unit_format'; + +export default { + components: { + GlGaugeChart, + }, + directives: { + GlResizeObserverDirective, + }, + props: { + graphData: { + type: Object, + required: true, + validator: graphDataValidatorForValues.bind(null, true), + }, + }, + data() { + return { + width: 0, + }; + }, + computed: { + rangeValues() { + let min = 0; + let max = 100; + + const { minValue, maxValue } = this.graphData; + + const isValidMinMax = () => { + return isFinite(minValue) && isFinite(maxValue) && minValue < maxValue; + }; + + if (isValidMinMax()) { + min = minValue; + max = maxValue; + } + + return { + min, + max, + }; + }, + validThresholds() { + const { mode, values } = this.graphData?.thresholds || {}; + const range = this.rangeValues; + + if (!isArray(values)) { + return []; + } + + return getValidThresholds({ mode, range, values }); + }, + queryResult() { + return this.graphData?.metrics[0]?.result[0]?.value[1]; + }, + splitValue() { + const { split } = this.graphData; + const defaultValue = 10; + + return isInteger(split) && split > 0 ? split : defaultValue; + }, + textValue() { + const formatFromPanel = this.graphData.format; + const defaultFormat = SUPPORTED_FORMATS.engineering; + const format = SUPPORTED_FORMATS[formatFromPanel] ?? defaultFormat; + const { queryResult } = this; + + const formatter = getFormatter(format); + + return isFinite(queryResult) ? formatter(queryResult) : '--'; + }, + thresholdsValue() { + /** + * If there are no valid thresholds, a default threshold + * will be set at 90% of the gauge arcs' max value + */ + const { min, max } = this.rangeValues; + + const defaultThresholdValue = [(max - min) * 0.95]; + return this.validThresholds.length ? this.validThresholds : defaultThresholdValue; + }, + value() { + /** + * The gauge chart gitlab-ui component expects a value + * of type number. + * + * So, if the query result is undefined, + * we pass the gauge chart a value of NaN. + */ + return this.queryResult || NaN; + }, + }, + methods: { + onResize() { + if (!this.$refs.gaugeChart) return; + const { width } = this.$refs.gaugeChart.$el.getBoundingClientRect(); + this.width = width; + }, + }, +}; +</script> +<template> + <div v-gl-resize-observer-directive="onResize"> + <gl-gauge-chart + ref="gaugeChart" + v-bind="$attrs" + :value="value" + :min="rangeValues.min" + :max="rangeValues.max" + :thresholds="thresholdsValue" + :text="textValue" + :split-number="splitValue" + :width="width" + /> + </div> +</template> diff --git a/app/assets/javascripts/monitoring/components/charts/heatmap.vue b/app/assets/javascripts/monitoring/components/charts/heatmap.vue index ddb44f7b1be..7003e2d37cf 100644 --- a/app/assets/javascripts/monitoring/components/charts/heatmap.vue +++ b/app/assets/javascripts/monitoring/components/charts/heatmap.vue @@ -36,7 +36,7 @@ export default { ); }, xAxisName() { - return this.graphData.x_label || ''; + return this.graphData.xLabel || ''; }, yAxisName() { return this.graphData.y_label || ''; diff --git a/app/assets/javascripts/monitoring/components/charts/options.js b/app/assets/javascripts/monitoring/components/charts/options.js index 42252dd5897..0cd4a02311c 100644 --- a/app/assets/javascripts/monitoring/components/charts/options.js +++ b/app/assets/javascripts/monitoring/components/charts/options.js @@ -1,6 +1,8 @@ +import { isFinite, uniq, sortBy, includes } from 'lodash'; import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format'; import { __, s__ } from '~/locale'; import { formatDate, timezones, formats } from '../../format_date'; +import { thresholdModeTypes } from '../../constants'; const yAxisBoundaryGap = [0.1, 0.1]; /** @@ -109,3 +111,65 @@ export const getTooltipFormatter = ({ const formatter = getFormatter(format); return num => formatter(num, precision); }; + +// Thresholds + +/** + * + * Used to find valid thresholds for the gauge chart + * + * An array of thresholds values is + * - duplicate values are removed; + * - filtered for invalid values; + * - sorted in ascending order; + * - only first two values are used. + */ +export const getValidThresholds = ({ mode, range = {}, values = [] } = {}) => { + const supportedModes = [thresholdModeTypes.ABSOLUTE, thresholdModeTypes.PERCENTAGE]; + const { min, max } = range; + + /** + * return early if min and max have invalid values + * or mode has invalid value + */ + if (!isFinite(min) || !isFinite(max) || min >= max || !includes(supportedModes, mode)) { + return []; + } + + const uniqueThresholds = uniq(values); + + const numberThresholds = uniqueThresholds.filter(threshold => isFinite(threshold)); + + const validThresholds = numberThresholds.filter(threshold => { + let isValid; + + if (mode === thresholdModeTypes.PERCENTAGE) { + isValid = threshold > 0 && threshold < 100; + } else if (mode === thresholdModeTypes.ABSOLUTE) { + isValid = threshold > min && threshold < max; + } + + return isValid; + }); + + const transformedThresholds = validThresholds.map(threshold => { + let transformedThreshold; + + if (mode === 'percentage') { + transformedThreshold = (threshold / 100) * (max - min); + } else { + transformedThreshold = threshold; + } + + return transformedThreshold; + }); + + const sortedThresholds = sortBy(transformedThresholds); + + const reducedThresholdsArray = + sortedThresholds.length > 2 + ? [sortedThresholds[0], sortedThresholds[1]] + : [...sortedThresholds]; + + return reducedThresholdsArray; +}; diff --git a/app/assets/javascripts/monitoring/components/charts/single_stat.vue b/app/assets/javascripts/monitoring/components/charts/single_stat.vue index 106c76a97dc..a8ab41ebf26 100644 --- a/app/assets/javascripts/monitoring/components/charts/single_stat.vue +++ b/app/assets/javascripts/monitoring/components/charts/single_stat.vue @@ -50,7 +50,7 @@ export default { } formatter = getFormatter(SUPPORTED_FORMATS.number); - return `${formatter(this.queryResult, defaultPrecision)}${this.queryInfo.unit}`; + return `${formatter(this.queryResult, defaultPrecision)}${this.queryInfo.unit ?? ''}`; }, graphTitle() { return this.queryInfo.label; diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue index f2add429a80..054111c203e 100644 --- a/app/assets/javascripts/monitoring/components/charts/time_series.vue +++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue @@ -1,6 +1,6 @@ <script> -import { omit, throttle } from 'lodash'; -import { GlLink, GlDeprecatedButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui'; +import { isEmpty, omit, throttle } from 'lodash'; +import { GlLink, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui'; import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import { s__ } from '~/locale'; import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; @@ -25,7 +25,6 @@ export default { GlAreaChart, GlLineChart, GlTooltip, - GlDeprecatedButton, GlChartSeriesLabel, GlLink, Icon, @@ -45,6 +44,11 @@ export default { required: false, default: () => ({}), }, + timeRange: { + type: Object, + required: false, + default: () => ({}), + }, seriesConfig: { type: Object, required: false, @@ -174,10 +178,17 @@ export default { chartOptions() { const { yAxis, xAxis } = this.option; const option = omit(this.option, ['series', 'yAxis', 'xAxis']); + const xAxisBounds = isEmpty(this.timeRange) + ? {} + : { + min: this.timeRange.start, + max: this.timeRange.end, + }; const timeXAxis = { ...getTimeAxisOptions({ timezone: this.timezone }), ...xAxis, + ...xAxisBounds, }; const dataYAxis = { |