dashboard.vue 9.47 KB
Newer Older
1
<script>
2 3 4 5 6 7 8 9
import {
  GlButton,
  GlDropdown,
  GlDropdownItem,
  GlModal,
  GlModalDirective,
  GlLink,
} from '@gitlab/ui';
10
import _ from 'underscore';
11
import { mapActions, mapState } from 'vuex';
12 13
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
14
import '~/vue_shared/mixins/is_ee';
15
import { getParameterValues } from '~/lib/utils/url_utility';
Adriel Santiago's avatar
Adriel Santiago committed
16
import MonitorAreaChart from './charts/area.vue';
17 18
import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue';
19
import { timeWindows, timeWindowsKeyNames } from '../constants';
20
import { getTimeDiff } from '../utils';
21

22 23 24
const sidebarAnimationDuration = 150;
let sidebarMutationObserver;

25 26
export default {
  components: {
Adriel Santiago's avatar
Adriel Santiago committed
27
    MonitorAreaChart,
28 29
    GraphGroup,
    EmptyState,
30
    Icon,
31
    GlButton,
32 33
    GlDropdown,
    GlDropdownItem,
34
    GlLink,
35 36 37 38
    GlModal,
  },
  directives: {
    GlModalDirective,
39 40
  },
  props: {
41 42 43 44 45
    externalDashboardPath: {
      type: String,
      required: false,
      default: '',
    },
46 47 48 49
    hasMetrics: {
      type: Boolean,
      required: false,
      default: true,
Filipa Lacerda's avatar
Filipa Lacerda committed
50
    },
51 52 53 54
    showPanels: {
      type: Boolean,
      required: false,
      default: true,
55
    },
56 57 58
    documentationPath: {
      type: String,
      required: true,
59
    },
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
    settingsPath: {
      type: String,
      required: true,
    },
    clustersPath: {
      type: String,
      required: true,
    },
    tagsPath: {
      type: String,
      required: true,
    },
    projectPath: {
      type: String,
      required: true,
    },
    metricsEndpoint: {
      type: String,
      required: true,
    },
    deploymentEndpoint: {
      type: String,
      required: false,
      default: null,
    },
    emptyGettingStartedSvgPath: {
      type: String,
      required: true,
    },
    emptyLoadingSvgPath: {
      type: String,
      required: true,
    },
    emptyNoDataSvgPath: {
      type: String,
      required: true,
    },
    emptyUnableToConnectSvgPath: {
      type: String,
      required: true,
    },
101 102 103 104
    environmentsEndpoint: {
      type: String,
      required: true,
    },
105 106 107 108
    currentEnvironmentName: {
      type: String,
      required: true,
    },
109 110 111 112
    showTimeWindowDropdown: {
      type: Boolean,
      required: true,
    },
113 114 115 116 117 118 119 120 121 122 123 124 125
    customMetricsAvailable: {
      type: Boolean,
      required: false,
      default: false,
    },
    customMetricsPath: {
      type: String,
      required: true,
    },
    validateQueryPath: {
      type: String,
      required: true,
    },
126 127 128 129
  },
  data() {
    return {
      state: 'gettingStarted',
130
      elWidth: 0,
131
      selectedTimeWindow: '',
132
      selectedTimeWindowKey: '',
133
      formIsValid: null,
134 135
    };
  },
136 137 138 139
  computed: {
    canAddMetrics() {
      return this.customMetricsAvailable && this.customMetricsPath.length;
    },
140 141 142 143 144 145 146
    ...mapState('monitoringDashboard', [
      'groups',
      'emptyState',
      'showEmptyState',
      'environments',
      'deploymentData',
    ]),
147
  },
148
  created() {
149
    this.setEndpoints({
150
      metricsEndpoint: this.metricsEndpoint,
151
      environmentsEndpoint: this.environmentsEndpoint,
152
      deploymentsEndpoint: this.deploymentEndpoint,
153
    });
154

155
    this.timeWindows = timeWindows;
156 157 158 159 160 161 162 163 164
    this.selectedTimeWindowKey =
      _.escape(getParameterValues('time_window')[0]) || timeWindowsKeyNames.eightHours;

    // Set default time window if the selectedTimeWindowKey is bogus
    if (!Object.keys(this.timeWindows).includes(this.selectedTimeWindowKey)) {
      this.selectedTimeWindowKey = timeWindowsKeyNames.eightHours;
    }

    this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey];
165 166
  },
  beforeDestroy() {
167 168 169
    if (sidebarMutationObserver) {
      sidebarMutationObserver.disconnect();
    }
170 171 172
  },
  mounted() {
    if (!this.hasMetrics) {
173
      this.setGettingStartedEmptyState();
174
    } else {
175 176
      this.fetchData(getTimeDiff(this.timeWindows.eightHours));

177 178 179 180 181 182
      sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
      sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
        attributes: true,
        childList: false,
        subtree: false,
      });
183 184 185
    }
  },
  methods: {
186 187 188 189 190
    ...mapActions('monitoringDashboard', [
      'fetchData',
      'setGettingStartedEmptyState',
      'setEndpoints',
    ]),
191 192 193 194 195 196 197
    getGraphAlerts(queries) {
      if (!this.allAlerts) return {};
      const metricIdsForChart = queries.map(q => q.metricId);
      return _.pick(this.allAlerts, alert => metricIdsForChart.includes(alert.metricId));
    },
    getGraphAlertValues(queries) {
      return Object.values(this.getGraphAlerts(queries));
198
    },
199 200 201
    hideAddMetricModal() {
      this.$refs.addMetricModal.hide();
    },
202 203 204 205
    onSidebarMutation() {
      setTimeout(() => {
        this.elWidth = this.$el.clientWidth;
      }, sidebarAnimationDuration);
Filipa Lacerda's avatar
Filipa Lacerda committed
206
    },
207 208 209 210 211 212
    setFormValidity(isValid) {
      this.formIsValid = isValid;
    },
    submitCustomMetricsForm() {
      this.$refs.customMetricsForm.submit();
    },
213 214 215
    activeTimeWindow(key) {
      return this.timeWindows[key] === this.selectedTimeWindow;
    },
216 217 218
    setTimeWindowParameter(key) {
      return `?time_window=${key}`;
    },
219
  },
220 221 222 223
  addMetric: {
    title: s__('Metrics|Add metric'),
    modalId: 'add-metric',
  },
224
};
225
</script>
226

227
<template>
228 229 230 231 232 233 234 235 236 237 238 239
  <div v-if="!showEmptyState" class="prometheus-graphs">
    <div class="gl-p-3 border-bottom bg-gray-light d-flex justify-content-between">
      <div
        v-if="environmentsEndpoint"
        class="dropdowns d-flex align-items-center justify-content-between"
      >
        <div class="d-flex align-items-center">
          <strong>{{ s__('Metrics|Environment') }}</strong>
          <gl-dropdown
            class="prepend-left-10 js-environments-dropdown"
            toggle-class="dropdown-menu-toggle"
            :text="currentEnvironmentName"
240
            :disabled="environments.length === 0"
241 242
          >
            <gl-dropdown-item
243
              v-for="environment in environments"
244 245 246 247 248 249 250
              :key="environment.id"
              :active="environment.name === currentEnvironmentName"
              active-class="is-active"
              >{{ environment.name }}</gl-dropdown-item
            >
          </gl-dropdown>
        </div>
251
        <div v-if="showTimeWindowDropdown" class="d-flex align-items-center prepend-left-8">
252 253 254 255 256
          <strong>{{ s__('Metrics|Show last') }}</strong>
          <gl-dropdown
            class="prepend-left-10 js-time-window-dropdown"
            toggle-class="dropdown-menu-toggle"
            :text="selectedTimeWindow"
257
          >
258 259 260 261 262 263 264 265
            <gl-dropdown-item
              v-for="(value, key) in timeWindows"
              :key="key"
              :active="activeTimeWindow(key)"
              ><gl-link :href="setTimeWindowParameter(key)">{{ value }}</gl-link></gl-dropdown-item
            >
          </gl-dropdown>
        </div>
266
      </div>
267 268 269 270 271
      <div class="d-flex">
        <div v-if="isEE && canAddMetrics">
          <gl-button
            v-gl-modal-directive="$options.addMetric.modalId"
            class="js-add-metric-button text-success border-success"
272
          >
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
            {{ $options.addMetric.title }}
          </gl-button>
          <gl-modal
            ref="addMetricModal"
            :modal-id="$options.addMetric.modalId"
            :title="$options.addMetric.title"
          >
            <form ref="customMetricsForm" :action="customMetricsPath" method="post">
              <custom-metrics-form-fields
                :validate-query-path="validateQueryPath"
                form-operation="post"
                @formValidation="setFormValidity"
              />
            </form>
            <div slot="modal-footer">
              <gl-button @click="hideAddMetricModal">
                {{ __('Cancel') }}
              </gl-button>
              <gl-button
                :disabled="!formIsValid"
                variant="success"
                @click="submitCustomMetricsForm"
              >
                {{ __('Save changes') }}
              </gl-button>
            </div>
          </gl-modal>
        </div>
        <gl-button
          v-if="externalDashboardPath.length"
          class="js-external-dashboard-link prepend-left-8"
          variant="primary"
          :href="externalDashboardPath"
        >
          {{ __('View full dashboard') }}
          <icon name="external-link" />
        </gl-button>
310
      </div>
Jose's avatar
Jose committed
311
    </div>
312
    <graph-group
313
      v-for="(groupData, index) in groups"
314
      :key="index"
315
      :name="groupData.group"
316
      :show-panels="showPanels"
317
    >
Adriel Santiago's avatar
Adriel Santiago committed
318
      <monitor-area-chart
319 320
        v-for="(graphData, graphIndex) in groupData.metrics"
        :key="graphIndex"
321
        :graph-data="graphData"
322
        :deployment-data="deploymentData"
323
        :thresholds="getGraphAlertValues(graphData.queries)"
324
        :container-width="elWidth"
325
        group-id="monitor-area-chart"
326 327
      >
        <alert-widget
328
          v-if="isEE && prometheusAlertsAvailable && alertsEndpoint && graphData"
329
          :alerts-endpoint="alertsEndpoint"
330 331
          :relevant-queries="graphData.queries"
          :alerts-to-manage="getGraphAlerts(graphData.queries)"
332 333 334
          @setAlerts="setAlerts"
        />
      </monitor-area-chart>
335
    </graph-group>
336
  </div>
337
  <empty-state
338
    v-else
339
    :selected-state="emptyState"
340 341
    :documentation-path="documentationPath"
    :settings-path="settingsPath"
342
    :clusters-path="clustersPath"
343 344
    :empty-getting-started-svg-path="emptyGettingStartedSvgPath"
    :empty-loading-svg-path="emptyLoadingSvgPath"
345
    :empty-no-data-svg-path="emptyNoDataSvgPath"
346
    :empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
347 348
  />
</template>