summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_metrics.js34
-rw-r--r--app/assets/javascripts/monitoring/components/embed.vue99
-rw-r--r--app/assets/javascripts/monitoring/components/embeds/embed_group.vue101
-rw-r--r--app/assets/javascripts/monitoring/components/embeds/metric_embed.vue131
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue22
-rw-r--r--app/assets/javascripts/monitoring/stores/embed_group/actions.js5
-rw-r--r--app/assets/javascripts/monitoring/stores/embed_group/getters.js4
-rw-r--r--app/assets/javascripts/monitoring/stores/embed_group/index.js24
-rw-r--r--app/assets/javascripts/monitoring/stores/embed_group/mutation_types.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/embed_group/mutations.js7
-rw-r--r--app/assets/javascripts/monitoring/stores/embed_group/state.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/index.js16
-rw-r--r--app/assets/stylesheets/components/collapsible_card.scss9
-rw-r--r--app/views/admin/application_settings/metrics_and_profiling.html.haml2
14 files changed, 346 insertions, 114 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/render_metrics.js b/app/assets/javascripts/behaviors/markdown/render_metrics.js
index 8050604e6e7..9260a89bd52 100644
--- a/app/assets/javascripts/behaviors/markdown/render_metrics.js
+++ b/app/assets/javascripts/behaviors/markdown/render_metrics.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import Metrics from '~/monitoring/components/embed.vue';
-import { createStore } from '~/monitoring/stores';
+import EmbedGroup from '~/monitoring/components/embeds/embed_group.vue';
+import { createStore } from '~/monitoring/stores/embed_group/';
// TODO: Handle copy-pasting - https://gitlab.com/gitlab-org/gitlab-foss/issues/64369.
export default function renderMetrics(elements) {
@@ -8,16 +8,36 @@ export default function renderMetrics(elements) {
return;
}
+ const EmbedGroupComponent = Vue.extend(EmbedGroup);
+
+ const wrapperList = [];
+
elements.forEach(element => {
- const { dashboardUrl } = element.dataset;
- const MetricsComponent = Vue.extend(Metrics);
+ let wrapper;
+ const { previousElementSibling } = element;
+ const isFirstElementInGroup = !previousElementSibling?.urls;
+
+ if (isFirstElementInGroup) {
+ wrapper = document.createElement('div');
+ wrapper.urls = [element.dataset.dashboardUrl];
+ element.parentNode.insertBefore(wrapper, element);
+ wrapperList.push(wrapper);
+ } else {
+ wrapper = previousElementSibling;
+ wrapper.urls.push(element.dataset.dashboardUrl);
+ }
+
+ // Clean up processed element
+ element.parentNode.removeChild(element);
+ });
+ wrapperList.forEach(wrapper => {
// eslint-disable-next-line no-new
- new MetricsComponent({
- el: element,
+ new EmbedGroupComponent({
+ el: wrapper,
store: createStore(),
propsData: {
- dashboardUrl,
+ urls: wrapper.urls,
},
});
});
diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue
deleted file mode 100644
index 6182b570e76..00000000000
--- a/app/assets/javascripts/monitoring/components/embed.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<script>
-import { mapActions, mapState, mapGetters } from 'vuex';
-import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
-import { convertToFixedRange } from '~/lib/utils/datetime_range';
-import { timeRangeFromUrl, removeTimeRangeParams } from '../utils';
-import { sidebarAnimationDuration } from '../constants';
-import { defaultTimeRange } from '~/vue_shared/constants';
-
-let sidebarMutationObserver;
-
-export default {
- components: {
- PanelType,
- },
- props: {
- dashboardUrl: {
- type: String,
- required: true,
- },
- },
- data() {
- const timeRange = timeRangeFromUrl(this.dashboardUrl) || defaultTimeRange;
- return {
- timeRange: convertToFixedRange(timeRange),
- elWidth: 0,
- };
- },
- computed: {
- ...mapState('monitoringDashboard', ['dashboard']),
- ...mapGetters('monitoringDashboard', ['metricsWithData']),
- charts() {
- if (!this.dashboard || !this.dashboard.panelGroups) {
- return [];
- }
- const groupWithMetrics = this.dashboard.panelGroups.find(group =>
- group.panels.find(chart => this.chartHasData(chart)),
- ) || { panels: [] };
-
- return groupWithMetrics.panels.filter(chart => this.chartHasData(chart));
- },
- isSingleChart() {
- return this.charts.length === 1;
- },
- },
- mounted() {
- this.setInitialState();
- this.setTimeRange(this.timeRange);
- this.fetchDashboard();
-
- sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
- sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
- attributes: true,
- childList: false,
- subtree: false,
- });
- },
- beforeDestroy() {
- if (sidebarMutationObserver) {
- sidebarMutationObserver.disconnect();
- }
- },
- methods: {
- ...mapActions('monitoringDashboard', [
- 'setTimeRange',
- 'fetchDashboard',
- 'setEndpoints',
- 'setFeatureFlags',
- 'setShowErrorBanner',
- ]),
- chartHasData(chart) {
- return chart.metrics.some(metric => this.metricsWithData().includes(metric.metricId));
- },
- onSidebarMutation() {
- setTimeout(() => {
- this.elWidth = this.$el.clientWidth;
- }, sidebarAnimationDuration);
- },
- setInitialState() {
- this.setEndpoints({
- dashboardEndpoint: removeTimeRangeParams(this.dashboardUrl),
- });
- this.setShowErrorBanner(false);
- },
- },
-};
-</script>
-<template>
- <div class="metrics-embed" :class="{ 'd-inline-flex col-lg-6 p-0': isSingleChart }">
- <div v-if="charts.length" class="row w-100 m-n2 pb-4">
- <panel-type
- v-for="(graphData, graphIndex) in charts"
- :key="`panel-type-${graphIndex}`"
- class="w-100"
- :graph-data="graphData"
- :group-id="dashboardUrl"
- />
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/monitoring/components/embeds/embed_group.vue b/app/assets/javascripts/monitoring/components/embeds/embed_group.vue
new file mode 100644
index 00000000000..b8562afe441
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/embeds/embed_group.vue
@@ -0,0 +1,101 @@
+<script>
+import { mapState, mapActions, mapGetters } from 'vuex';
+import sum from 'lodash/sum';
+import { GlButton, GlCard, GlIcon } from '@gitlab/ui';
+import { n__ } from '~/locale';
+import { monitoringDashboard } from '~/monitoring/stores';
+import MetricEmbed from './metric_embed.vue';
+
+export default {
+ components: {
+ GlButton,
+ GlCard,
+ GlIcon,
+ MetricEmbed,
+ },
+ props: {
+ urls: {
+ type: Array,
+ required: true,
+ validator: urls => urls.length > 0,
+ },
+ },
+ data() {
+ return {
+ isCollapsed: false,
+ };
+ },
+ computed: {
+ ...mapState('embedGroup', ['module']),
+ ...mapGetters('embedGroup', ['metricsWithData']),
+ arrowIconName() {
+ return this.isCollapsed ? 'chevron-right' : 'chevron-down';
+ },
+ bodyClass() {
+ return ['border-top', 'pl-3', 'pt-3', { 'd-none': this.isCollapsed }];
+ },
+ buttonLabel() {
+ return this.isCollapsed
+ ? n__('View chart', 'View charts', this.numCharts)
+ : n__('Hide chart', 'Hide charts', this.numCharts);
+ },
+ containerClass() {
+ return this.isSingleChart ? 'col-lg-12' : 'col-lg-6';
+ },
+ numCharts() {
+ if (this.metricsWithData === null) {
+ return 0;
+ }
+ return sum(this.metricsWithData);
+ },
+ isSingleChart() {
+ return this.numCharts === 1;
+ },
+ },
+ created() {
+ this.urls.forEach((url, index) => {
+ const name = this.getNamespace(index);
+ this.$store.registerModule(name, monitoringDashboard);
+ this.addModule(name);
+ });
+ },
+ methods: {
+ ...mapActions('embedGroup', ['addModule']),
+ getNamespace(id) {
+ return `monitoringDashboard/${id}`;
+ },
+ toggleCollapsed() {
+ this.isCollapsed = !this.isCollapsed;
+ },
+ },
+};
+</script>
+<template>
+ <gl-card
+ v-show="numCharts > 0"
+ class="collapsible-card border p-0 mb-3"
+ header-class="d-flex align-items-center border-bottom-0 py-2"
+ :body-class="bodyClass"
+ >
+ <template #header>
+ <gl-button
+ class="collapsible-card-btn d-flex text-decoration-none"
+ :aria-label="buttonLabel"
+ variant="link"
+ @click="toggleCollapsed"
+ >
+ <gl-icon class="mr-1" :name="arrowIconName" />
+ {{ buttonLabel }}
+ </gl-button>
+ </template>
+ <div class="d-flex flex-wrap">
+ <metric-embed
+ v-for="(url, index) in urls"
+ :key="`${index}/${url}`"
+ :dashboard-url="url"
+ :namespace="getNamespace(index)"
+ :container-class="containerClass"
+ />
+ </div>
+ </gl-card>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/embeds/metric_embed.vue b/app/assets/javascripts/monitoring/components/embeds/metric_embed.vue
new file mode 100644
index 00000000000..8a44e6bd737
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/embeds/metric_embed.vue
@@ -0,0 +1,131 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
+import { convertToFixedRange } from '~/lib/utils/datetime_range';
+import { defaultTimeRange } from '~/vue_shared/constants';
+import { timeRangeFromUrl, removeTimeRangeParams } from '../../utils';
+import { sidebarAnimationDuration } from '../../constants';
+
+let sidebarMutationObserver;
+
+export default {
+ components: {
+ PanelType,
+ },
+ props: {
+ containerClass: {
+ type: String,
+ required: false,
+ default: 'col-lg-12',
+ },
+ dashboardUrl: {
+ type: String,
+ required: true,
+ },
+ namespace: {
+ type: String,
+ required: false,
+ default: 'monitoringDashboard',
+ },
+ },
+ data() {
+ const timeRange = timeRangeFromUrl(this.dashboardUrl) || defaultTimeRange;
+ return {
+ timeRange: convertToFixedRange(timeRange),
+ elWidth: 0,
+ };
+ },
+ computed: {
+ ...mapState({
+ dashboard(state) {
+ return state[this.namespace].dashboard;
+ },
+ metricsWithData(state, getters) {
+ return getters[`${this.namespace}/metricsWithData`]();
+ },
+ }),
+ charts() {
+ if (!this.dashboard || !this.dashboard.panelGroups) {
+ return [];
+ }
+ return this.dashboard.panelGroups.reduce(
+ (acc, currentGroup) => acc.concat(currentGroup.panels.filter(this.chartHasData)),
+ [],
+ );
+ },
+ isSingleChart() {
+ return this.charts.length === 1;
+ },
+ embedClass() {
+ return this.isSingleChart ? this.containerClass : 'col-lg-12';
+ },
+ panelClass() {
+ return this.isSingleChart ? 'col-lg-12' : 'col-lg-6';
+ },
+ },
+ mounted() {
+ this.setInitialState();
+ this.setTimeRange(this.timeRange);
+ this.fetchDashboard();
+
+ sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
+ sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
+ attributes: true,
+ childList: false,
+ subtree: false,
+ });
+ },
+ beforeDestroy() {
+ if (sidebarMutationObserver) {
+ sidebarMutationObserver.disconnect();
+ }
+ },
+ methods: {
+ // Use function args to support dynamic namespaces in mapXXX helpers. Pattern described
+ // in https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
+ ...mapActions({
+ setTimeRange(dispatch, payload) {
+ return dispatch(`${this.namespace}/setTimeRange`, payload);
+ },
+ fetchDashboard(dispatch, payload) {
+ return dispatch(`${this.namespace}/fetchDashboard`, payload);
+ },
+ setEndpoints(dispatch, payload) {
+ return dispatch(`${this.namespace}/setEndpoints`, payload);
+ },
+ setFeatureFlags(dispatch, payload) {
+ return dispatch(`${this.namespace}/setFeatureFlags`, payload);
+ },
+ setShowErrorBanner(dispatch, payload) {
+ return dispatch(`${this.namespace}/setShowErrorBanner`, payload);
+ },
+ }),
+ chartHasData(chart) {
+ return chart.metrics.some(metric => this.metricsWithData.includes(metric.metricId));
+ },
+ onSidebarMutation() {
+ setTimeout(() => {
+ this.elWidth = this.$el.clientWidth;
+ }, sidebarAnimationDuration);
+ },
+ setInitialState() {
+ this.setEndpoints({
+ dashboardEndpoint: removeTimeRangeParams(this.dashboardUrl),
+ });
+ this.setShowErrorBanner(false);
+ },
+ },
+};
+</script>
+<template>
+ <div class="metrics-embed p-0 d-flex flex-wrap" :class="embedClass">
+ <panel-type
+ v-for="(graphData, graphIndex) in charts"
+ :key="`panel-type-${graphIndex}`"
+ :class="panelClass"
+ :graph-data="graphData"
+ :group-id="dashboardUrl"
+ :namespace="namespace"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index da305c7dda3..d6d60c2d5da 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -68,6 +68,11 @@ export default {
required: false,
default: 'panel-type-chart',
},
+ namespace: {
+ type: String,
+ required: false,
+ default: 'monitoringDashboard',
+ },
},
data() {
return {
@@ -76,7 +81,22 @@ export default {
};
},
computed: {
- ...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']),
+ // Use functions to support dynamic namespaces in mapXXX helpers. Pattern described
+ // in https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
+ ...mapState({
+ deploymentData(state) {
+ return state[this.namespace].deploymentData;
+ },
+ projectPath(state) {
+ return state[this.namespace].projectPath;
+ },
+ logsPath(state) {
+ return state[this.namespace].logsPath;
+ },
+ timeRange(state) {
+ return state[this.namespace].timeRange;
+ },
+ }),
title() {
return this.graphData.title || '';
},
diff --git a/app/assets/javascripts/monitoring/stores/embed_group/actions.js b/app/assets/javascripts/monitoring/stores/embed_group/actions.js
new file mode 100644
index 00000000000..cbe0950d954
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/embed_group/actions.js
@@ -0,0 +1,5 @@
+import * as types from './mutation_types';
+
+export const addModule = ({ commit }, data) => commit(types.ADD_MODULE, data);
+
+export default () => {};
diff --git a/app/assets/javascripts/monitoring/stores/embed_group/getters.js b/app/assets/javascripts/monitoring/stores/embed_group/getters.js
new file mode 100644
index 00000000000..9b08cf762c1
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/embed_group/getters.js
@@ -0,0 +1,4 @@
+export const metricsWithData = (state, getters, rootState, rootGetters) =>
+ state.modules.map(module => rootGetters[`${module}/metricsWithData`]().length);
+
+export default () => {};
diff --git a/app/assets/javascripts/monitoring/stores/embed_group/index.js b/app/assets/javascripts/monitoring/stores/embed_group/index.js
new file mode 100644
index 00000000000..773bca9f87e
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/embed_group/index.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+import state from './state';
+
+Vue.use(Vuex);
+
+// In practice this store will have a number of `monitoringDashboard` modules added dynamically
+export const createStore = () =>
+ new Vuex.Store({
+ modules: {
+ embedGroup: {
+ namespaced: true,
+ actions,
+ getters,
+ mutations,
+ state,
+ },
+ },
+ });
+
+export default createStore();
diff --git a/app/assets/javascripts/monitoring/stores/embed_group/mutation_types.js b/app/assets/javascripts/monitoring/stores/embed_group/mutation_types.js
new file mode 100644
index 00000000000..e7a425d3623
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/embed_group/mutation_types.js
@@ -0,0 +1,3 @@
+export const ADD_MODULE = 'ADD_MODULE';
+
+export default () => {};
diff --git a/app/assets/javascripts/monitoring/stores/embed_group/mutations.js b/app/assets/javascripts/monitoring/stores/embed_group/mutations.js
new file mode 100644
index 00000000000..3c66129f239
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/embed_group/mutations.js
@@ -0,0 +1,7 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.ADD_MODULE](state, module) {
+ state.modules.push(module);
+ },
+};
diff --git a/app/assets/javascripts/monitoring/stores/embed_group/state.js b/app/assets/javascripts/monitoring/stores/embed_group/state.js
new file mode 100644
index 00000000000..016c7e5dac7
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/embed_group/state.js
@@ -0,0 +1,3 @@
+export default () => ({
+ modules: [],
+});
diff --git a/app/assets/javascripts/monitoring/stores/index.js b/app/assets/javascripts/monitoring/stores/index.js
index c1c466b7cf0..f08a6402aa6 100644
--- a/app/assets/javascripts/monitoring/stores/index.js
+++ b/app/assets/javascripts/monitoring/stores/index.js
@@ -7,16 +7,18 @@ import state from './state';
Vue.use(Vuex);
+export const monitoringDashboard = {
+ namespaced: true,
+ actions,
+ getters,
+ mutations,
+ state,
+};
+
export const createStore = () =>
new Vuex.Store({
modules: {
- monitoringDashboard: {
- namespaced: true,
- actions,
- getters,
- mutations,
- state,
- },
+ monitoringDashboard,
},
});
diff --git a/app/assets/stylesheets/components/collapsible_card.scss b/app/assets/stylesheets/components/collapsible_card.scss
new file mode 100644
index 00000000000..c7c7423c1cd
--- /dev/null
+++ b/app/assets/stylesheets/components/collapsible_card.scss
@@ -0,0 +1,9 @@
+.collapsible-card {
+ .collapsible-card-btn {
+ color: $gl-text-color;
+
+ &:hover {
+ color: $blue-600;
+ }
+ }
+}
diff --git a/app/views/admin/application_settings/metrics_and_profiling.html.haml b/app/views/admin/application_settings/metrics_and_profiling.html.haml
index 0b747082de0..6a703d0b70c 100644
--- a/app/views/admin/application_settings/metrics_and_profiling.html.haml
+++ b/app/views/admin/application_settings/metrics_and_profiling.html.haml
@@ -60,4 +60,6 @@
.settings-content
= render 'usage'
+= render_if_exists 'admin/application_settings/seat_link_setting', expanded: expanded_by_default?
+
= render_if_exists 'admin/application_settings/pseudonymizer_settings', expanded: expanded_by_default?