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')
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue71
-rw-r--r--app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue151
-rw-r--r--app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue77
-rw-r--r--app/assets/javascripts/monitoring/constants.js10
-rw-r--r--app/assets/javascripts/monitoring/utils.js47
-rw-r--r--app/assets/stylesheets/pages/prometheus.scss14
6 files changed, 325 insertions, 45 deletions
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 2e35ef8d4b0..b4ea415bb51 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -12,23 +12,19 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import { __, s__ } from '~/locale';
+import createFlash from '~/flash';
import Icon from '~/vue_shared/components/icon.vue';
-import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
+import { getParameterValues, mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
+import DateTimePicker from './date_time_picker/date_time_picker.vue';
import MonitorTimeSeriesChart from './charts/time_series.vue';
import MonitorSingleStatChart from './charts/single_stat.vue';
import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue';
-import { sidebarAnimationDuration, timeWindows } from '../constants';
+import { sidebarAnimationDuration } from '../constants';
import TrackEventDirective from '~/vue_shared/directives/track_event';
-
-import {
- getTimeDiff,
- getTimeWindow,
- downloadCSVOptions,
- generateLinkToChartOptions,
-} from '../utils';
+import { getTimeDiff, isValidDate, downloadCSVOptions, generateLinkToChartOptions } from '../utils';
let sidebarMutationObserver;
@@ -46,6 +42,7 @@ export default {
GlDropdownItem,
GlFormGroup,
GlModal,
+ DateTimePicker,
},
directives: {
GlModal: GlModalDirective,
@@ -171,10 +168,8 @@ export default {
return {
state: 'gettingStarted',
elWidth: 0,
- selectedTimeWindow: '',
- selectedTimeWindowKey: '',
formIsValid: null,
- timeWindows: {},
+ selectedTimeWindow: {},
isRearrangingPanels: false,
};
},
@@ -237,11 +232,13 @@ export default {
end,
};
- this.timeWindows = timeWindows;
- this.selectedTimeWindowKey = getTimeWindow(range);
- this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey];
+ this.selectedTimeWindow = range;
- this.fetchData(range);
+ if (!isValidDate(start) || !isValidDate(end)) {
+ this.showInvalidDateError();
+ } else {
+ this.fetchData(range);
+ }
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
@@ -298,6 +295,9 @@ export default {
// See https://gitlab.com/gitlab-org/gitlab/issues/27835
metrics.splice(graphIndex, 1);
},
+ showInvalidDateError() {
+ createFlash(s__('Metrics|Link contains an invalid time window.'));
+ },
generateLink(group, title, yLabel) {
const dashboard = this.currentDashboard || this.firstDashboard.path;
const params = _.pick({ dashboard, group, title, y_label: yLabel }, value => value != null);
@@ -320,16 +320,12 @@ export default {
submitCustomMetricsForm() {
this.$refs.customMetricsForm.submit();
},
- activeTimeWindow(key) {
- return this.timeWindows[key] === this.selectedTimeWindow;
- },
- setTimeWindowParameter(key) {
- const { start, end } = getTimeDiff(key);
- return `?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`;
- },
groupHasData(group) {
return this.chartsWithData(group.metrics).length > 0;
},
+ onDateTimePickerApply(timeWindowUrlParams) {
+ return redirectTo(mergeUrlParams(timeWindowUrlParams, window.location.href));
+ },
downloadCSVOptions,
generateLinkToChartOptions,
},
@@ -342,14 +338,14 @@ export default {
<template>
<div class="prometheus-graphs">
- <div class="gl-p-3 pb-0 border-bottom bg-gray-light">
+ <div class="prometheus-graphs-header gl-p-3 pb-0 border-bottom bg-gray-light">
<div class="row">
<template v-if="environmentsEndpoint">
<gl-form-group
:label="__('Dashboard')"
label-size="sm"
label-for="monitor-dashboards-dropdown"
- class="col-sm-12 col-md-4 col-lg-2"
+ class="col-sm-12 col-md-6 col-lg-2"
>
<gl-dropdown
id="monitor-dashboards-dropdown"
@@ -372,7 +368,7 @@ export default {
:label="s__('Metrics|Environment')"
label-size="sm"
label-for="monitor-environments-dropdown"
- class="col-sm-6 col-md-4 col-lg-2"
+ class="col-sm-6 col-md-6 col-lg-2"
>
<gl-dropdown
id="monitor-environments-dropdown"
@@ -397,30 +393,19 @@ export default {
:label="s__('Metrics|Show last')"
label-size="sm"
label-for="monitor-time-window-dropdown"
- class="col-sm-6 col-md-4 col-lg-2"
+ class="col-sm-6 col-md-6 col-lg-4"
>
- <gl-dropdown
- id="monitor-time-window-dropdown"
- class="mb-0 d-flex js-time-window-dropdown"
- toggle-class="dropdown-menu-toggle"
- :text="selectedTimeWindow"
- >
- <gl-dropdown-item
- v-for="(value, key) in timeWindows"
- :key="key"
- :active="activeTimeWindow(key)"
- :href="setTimeWindowParameter(key)"
- active-class="active"
- >{{ value }}</gl-dropdown-item
- >
- </gl-dropdown>
+ <date-time-picker
+ :selected-time-window="selectedTimeWindow"
+ @onApply="onDateTimePickerApply"
+ />
</gl-form-group>
</template>
<gl-form-group
v-if="addingMetricsAvailable || showRearrangePanelsBtn || externalDashboardUrl.length"
label-for="prometheus-graphs-dropdown-buttons"
- class="dropdown-buttons col-lg d-lg-flex align-items-end"
+ class="dropdown-buttons col-md d-md-flex col-lg d-lg-flex align-items-end"
>
<div id="prometheus-graphs-dropdown-buttons">
<gl-button
diff --git a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue
new file mode 100644
index 00000000000..4616a767295
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue
@@ -0,0 +1,151 @@
+<script>
+import { GlButton, GlDropdown, GlDropdownItem, GlFormGroup } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import DateTimePickerInput from './date_time_picker_input.vue';
+import {
+ getTimeDiff,
+ getTimeWindow,
+ stringToISODate,
+ ISODateToString,
+ truncateZerosInDateTime,
+ isDateTimePickerInputValid,
+} from '~/monitoring/utils';
+import { timeWindows } from '~/monitoring/constants';
+
+export default {
+ components: {
+ Icon,
+ DateTimePickerInput,
+ GlFormGroup,
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ },
+ props: {
+ timeWindows: {
+ type: Object,
+ required: false,
+ default: () => timeWindows,
+ },
+ selectedTimeWindow: {
+ type: Object,
+ required: false,
+ default: () => {},
+ },
+ },
+ data() {
+ return {
+ selectedTimeWindowText: '',
+ customTime: {
+ from: null,
+ to: null,
+ },
+ };
+ },
+ computed: {
+ applyEnabled() {
+ return Boolean(this.inputState.from && this.inputState.to);
+ },
+ inputState() {
+ const { from, to } = this.customTime;
+ return {
+ from: from && isDateTimePickerInputValid(from),
+ to: to && isDateTimePickerInputValid(to),
+ };
+ },
+ },
+ mounted() {
+ const range = getTimeWindow(this.selectedTimeWindow);
+ if (range) {
+ this.selectedTimeWindowText = this.timeWindows[range];
+ } else {
+ this.customTime = {
+ from: truncateZerosInDateTime(ISODateToString(this.selectedTimeWindow.start)),
+ to: truncateZerosInDateTime(ISODateToString(this.selectedTimeWindow.end)),
+ };
+ this.selectedTimeWindowText = sprintf(s__('%{from} to %{to}'), this.customTime);
+ }
+ },
+ methods: {
+ activeTimeWindow(key) {
+ return this.timeWindows[key] === this.selectedTimeWindowText;
+ },
+ setCustomTimeWindowParameter() {
+ this.$emit('onApply', {
+ start: stringToISODate(this.customTime.from),
+ end: stringToISODate(this.customTime.to),
+ });
+ },
+ setTimeWindowParameter(key) {
+ const { start, end } = getTimeDiff(key);
+ this.$emit('onApply', {
+ start,
+ end,
+ });
+ },
+ closeDropdown() {
+ this.$refs.dropdown.hide();
+ },
+ },
+};
+</script>
+<template>
+ <gl-dropdown
+ ref="dropdown"
+ :text="selectedTimeWindowText"
+ menu-class="time-window-dropdown-menu"
+ class="js-time-window-dropdown"
+ >
+ <div class="d-flex justify-content-between time-window-dropdown-menu-container">
+ <gl-form-group
+ :label="__('Custom range')"
+ label-for="custom-from-time"
+ class="custom-time-range-form-group col-md-7 p-0 m-0"
+ >
+ <date-time-picker-input
+ id="custom-time-from"
+ v-model="customTime.from"
+ :label="__('From')"
+ :state="inputState.from"
+ />
+ <date-time-picker-input
+ id="custom-time-to"
+ v-model="customTime.to"
+ :label="__('To')"
+ :state="inputState.to"
+ />
+ <gl-form-group>
+ <gl-button @click="closeDropdown">{{ __('Cancel') }}</gl-button>
+ <gl-button
+ variant="success"
+ :disabled="!applyEnabled"
+ @click="setCustomTimeWindowParameter"
+ >{{ __('Apply') }}</gl-button
+ >
+ </gl-form-group>
+ </gl-form-group>
+ <gl-form-group
+ :label="__('Quick range')"
+ label-for="group-id-dropdown"
+ label-align="center"
+ class="col-md-4 p-0 m-0"
+ >
+ <gl-dropdown-item
+ v-for="(value, key) in timeWindows"
+ :key="key"
+ :active="activeTimeWindow(key)"
+ active-class="active"
+ @click="setTimeWindowParameter(key)"
+ >
+ <icon
+ name="mobile-issue-close"
+ class="align-bottom"
+ :class="{ invisible: !activeTimeWindow(key) }"
+ />
+ {{ value }}
+ </gl-dropdown-item>
+ </gl-form-group>
+ </div>
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue
new file mode 100644
index 00000000000..0388a6190d9
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue
@@ -0,0 +1,77 @@
+<script>
+import _ from 'underscore';
+import { s__, sprintf } from '~/locale';
+import { GlFormGroup, GlFormInput } from '@gitlab/ui';
+import { dateFormats } from '~/monitoring/constants';
+
+const inputGroupText = {
+ invalidFeedback: sprintf(s__('Format: %{dateFormat}'), {
+ dateFormat: dateFormats.dateTimePicker.format,
+ }),
+ placeholder: dateFormats.dateTimePicker.format,
+};
+
+export default {
+ components: {
+ GlFormGroup,
+ GlFormInput,
+ },
+ props: {
+ state: {
+ default: null,
+ required: true,
+ validator: prop => typeof prop === 'boolean' || prop === null,
+ },
+ value: {
+ default: null,
+ required: false,
+ validator: prop => typeof prop === 'string' || prop === null,
+ },
+ label: {
+ type: String,
+ default: '',
+ required: true,
+ },
+ id: {
+ type: String,
+ required: false,
+ default: () => _.uniqueId('dateTimePicker_'),
+ },
+ },
+ data() {
+ return {
+ inputGroupText,
+ };
+ },
+ computed: {
+ invalidFeedback() {
+ return this.state ? '' : this.inputGroupText.invalidFeedback;
+ },
+ inputState() {
+ // When the state is valid we want to show no
+ // green outline. Hence passing null and not true.
+ if (this.state === true) {
+ return null;
+ }
+ return this.state;
+ },
+ },
+ methods: {
+ onInputBlur(e) {
+ this.$emit('input', e.target.value.trim() || null);
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-form-group :label="label" label-size="sm" :label-for="id" :invalid-feedback="invalidFeedback">
+ <gl-form-input
+ :id="id"
+ :value="value"
+ :state="inputState"
+ :placeholder="inputGroupText.placeholder"
+ @blur="onInputBlur"
+ />
+ </gl-form-group>
+</template>
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
index 13aba3d9f44..2836fe4fc26 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -3,6 +3,11 @@ import { __ } from '~/locale';
export const sidebarAnimationDuration = 300; // milliseconds.
export const chartHeight = 300;
+/**
+ * Valid strings for this regex are
+ * 2019-10-01 and 2019-10-01 01:02:03
+ */
+export const dateTimePickerRegex = /^(\d{4})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])(?: (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))?$/;
export const graphTypes = {
deploymentData: 'scatter',
@@ -28,6 +33,11 @@ export const timeWindows = {
export const dateFormats = {
timeOfDay: 'h:MM TT',
default: 'dd mmm yyyy, h:MMTT',
+ dateTimePicker: {
+ format: 'yyyy-mm-dd hh:mm:ss',
+ ISODate: "yyyy-mm-dd'T'HH:MM:ss'Z'",
+ stringDate: 'yyyy-mm-dd HH:MM:ss',
+ },
};
export const secondsIn = {
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
index 9049695b992..4c72f5226b7 100644
--- a/app/assets/javascripts/monitoring/utils.js
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -1,4 +1,5 @@
-import { secondsIn, timeWindowsKeyNames } from './constants';
+import dateformat from 'dateformat';
+import { secondsIn, dateTimePickerRegex, dateFormats } from './constants';
const secondsToMilliseconds = seconds => seconds * 1000;
@@ -19,7 +20,49 @@ export const getTimeWindow = ({ start, end }) =>
return timeRange;
}
return acc;
- }, timeWindowsKeyNames.eightHours);
+ }, null);
+
+export const isDateTimePickerInputValid = val => dateTimePickerRegex.test(val);
+
+export const truncateZerosInDateTime = datetime => datetime.replace(' 00:00:00', '');
+
+/**
+ * The URL params start and end need to be validated
+ * before passing them down to other components.
+ *
+ * @param {string} dateString
+ */
+export const isValidDate = dateString => {
+ try {
+ // dateformat throws error that can be caught.
+ // This is better than using `new Date()`
+ if (dateString && dateString.trim()) {
+ dateformat(dateString, 'isoDateTime');
+ return true;
+ }
+ return false;
+ } catch {
+ return false;
+ }
+};
+
+/**
+ * Convert the input in Time picker component to ISO date.
+ *
+ * @param {string} val
+ * @returns {string}
+ */
+export const stringToISODate = val =>
+ dateformat(new Date(val.replace(/-/g, '/')), dateFormats.dateTimePicker.ISODate, true);
+
+/**
+ * Convert the ISO date received from the URL to string
+ * for the Time picker component.
+ *
+ * @param {Date} date
+ * @returns {string}
+ */
+export const ISODateToString = date => dateformat(date, dateFormats.dateTimePicker.stringDate);
/**
* This method is used to validate if the graph data format for a chart component
diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/pages/prometheus.scss
index ceafff94719..154e505f7a4 100644
--- a/app/assets/stylesheets/pages/prometheus.scss
+++ b/app/assets/stylesheets/pages/prometheus.scss
@@ -46,6 +46,20 @@
}
}
+.prometheus-graphs-header {
+ .time-window-dropdown-menu {
+ padding: $gl-padding $gl-padding 0 $gl-padding-12;
+ }
+
+ .time-window-dropdown-menu-container {
+ width: 360px;
+ }
+
+ .custom-time-range-form-group > label {
+ padding-bottom: $gl-padding;
+ }
+}
+
.prometheus-panel {
margin-top: 20px;
}