Commit 1dbe1911 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '217758-reduce-metrics-dashboard-loading' into 'master'

Replace loading state with an icon, show dashboard skeleton earlier

See merge request gitlab-org/gitlab!36399
parents a6a3355d 87d384a6
...@@ -161,7 +161,6 @@ export default { ...@@ -161,7 +161,6 @@ export default {
...mapState('monitoringDashboard', [ ...mapState('monitoringDashboard', [
'dashboard', 'dashboard',
'emptyState', 'emptyState',
'showEmptyState',
'expandedPanel', 'expandedPanel',
'variables', 'variables',
'links', 'links',
...@@ -169,6 +168,9 @@ export default { ...@@ -169,6 +168,9 @@ export default {
'hasDashboardValidationWarnings', 'hasDashboardValidationWarnings',
]), ]),
...mapGetters('monitoringDashboard', ['selectedDashboard', 'getMetricStates']), ...mapGetters('monitoringDashboard', ['selectedDashboard', 'getMetricStates']),
shouldShowEmptyState() {
return Boolean(this.emptyState);
},
shouldShowVariablesSection() { shouldShowVariablesSection() {
return Boolean(this.variables.length); return Boolean(this.variables.length);
}, },
...@@ -277,6 +279,14 @@ export default { ...@@ -277,6 +279,14 @@ export default {
} }
return null; return null;
}, },
/**
* Return true if the entire group is loading.
* @param {String} groupKey - Identifier for group
* @returns {boolean}
*/
isGroupLoading(groupKey) {
return this.groupSingleEmptyState(groupKey) === metricStates.LOADING;
},
/** /**
* A group should be not collapsed if any metric is loaded (OK) * A group should be not collapsed if any metric is loaded (OK)
* *
...@@ -412,9 +422,9 @@ export default { ...@@ -412,9 +422,9 @@ export default {
@dateTimePickerInvalid="onDateTimePickerInvalid" @dateTimePickerInvalid="onDateTimePickerInvalid"
@setRearrangingPanels="onSetRearrangingPanels" @setRearrangingPanels="onSetRearrangingPanels"
/> />
<variables-section v-if="shouldShowVariablesSection && !showEmptyState" /> <template v-if="!shouldShowEmptyState">
<links-section v-if="shouldShowLinksSection && !showEmptyState" /> <variables-section v-if="shouldShowVariablesSection" />
<div v-if="!showEmptyState"> <links-section v-if="shouldShowLinksSection" />
<dashboard-panel <dashboard-panel
v-show="expandedPanel.panel" v-show="expandedPanel.panel"
ref="expandedPanel" ref="expandedPanel"
...@@ -449,6 +459,7 @@ export default { ...@@ -449,6 +459,7 @@ export default {
:key="`${groupData.group}.${groupData.priority}`" :key="`${groupData.group}.${groupData.priority}`"
:name="groupData.group" :name="groupData.group"
:show-panels="showPanels" :show-panels="showPanels"
:is-loading="isGroupLoading(groupData.key)"
:collapse-group="collapseGroup(groupData.key)" :collapse-group="collapseGroup(groupData.key)"
> >
<vue-draggable <vue-draggable
...@@ -506,7 +517,7 @@ export default { ...@@ -506,7 +517,7 @@ export default {
</div> </div>
</graph-group> </graph-group>
</div> </div>
</div> </template>
<empty-state <empty-state
v-else v-else
:selected-state="emptyState" :selected-state="emptyState"
......
...@@ -119,10 +119,10 @@ export default { ...@@ -119,10 +119,10 @@ export default {
}, },
computed: { computed: {
...mapState('monitoringDashboard', [ ...mapState('monitoringDashboard', [
'emptyState',
'environmentsLoading', 'environmentsLoading',
'currentEnvironmentName', 'currentEnvironmentName',
'isUpdatingStarredValue', 'isUpdatingStarredValue',
'showEmptyState',
'dashboardTimezone', 'dashboardTimezone',
'projectPath', 'projectPath',
'canAccessOperationsSettings', 'canAccessOperationsSettings',
...@@ -132,13 +132,16 @@ export default { ...@@ -132,13 +132,16 @@ export default {
isOutOfTheBoxDashboard() { isOutOfTheBoxDashboard() {
return this.selectedDashboard?.out_of_the_box_dashboard; return this.selectedDashboard?.out_of_the_box_dashboard;
}, },
shouldShowEmptyState() {
return Boolean(this.emptyState);
},
shouldShowEnvironmentsDropdownNoMatchedMsg() { shouldShowEnvironmentsDropdownNoMatchedMsg() {
return !this.environmentsLoading && this.filteredEnvironments.length === 0; return !this.environmentsLoading && this.filteredEnvironments.length === 0;
}, },
addingMetricsAvailable() { addingMetricsAvailable() {
return ( return (
this.customMetricsAvailable && this.customMetricsAvailable &&
!this.showEmptyState && !this.shouldShowEmptyState &&
// Custom metrics only avaialble on system dashboards because // Custom metrics only avaialble on system dashboards because
// they are stored in the database. This can be improved. See: // they are stored in the database. This can be improved. See:
// https://gitlab.com/gitlab-org/gitlab/-/issues/28241 // https://gitlab.com/gitlab-org/gitlab/-/issues/28241
...@@ -146,7 +149,7 @@ export default { ...@@ -146,7 +149,7 @@ export default {
); );
}, },
showRearrangePanelsBtn() { showRearrangePanelsBtn() {
return !this.showEmptyState && this.rearrangePanelsAvailable; return !this.shouldShowEmptyState && this.rearrangePanelsAvailable;
}, },
displayUtc() { displayUtc() {
return this.dashboardTimezone === timezones.UTC; return this.dashboardTimezone === timezones.UTC;
......
<script> <script>
import { GlEmptyState } from '@gitlab/ui'; import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { dashboardEmptyStates } from '../constants'; import { dashboardEmptyStates } from '../constants';
export default { export default {
components: { components: {
GlLoadingIcon,
GlEmptyState, GlEmptyState,
}, },
props: { props: {
selectedState: {
type: String,
required: true,
validator: state => Object.values(dashboardEmptyStates).includes(state),
},
documentationPath: { documentationPath: {
type: String, type: String,
required: true, required: true,
...@@ -22,10 +28,6 @@ export default { ...@@ -22,10 +28,6 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
selectedState: {
type: String,
required: true,
},
emptyGettingStartedSvgPath: { emptyGettingStartedSvgPath: {
type: String, type: String,
required: true, required: true,
...@@ -54,52 +56,49 @@ export default { ...@@ -54,52 +56,49 @@ export default {
}, },
data() { data() {
return { return {
/**
* Possible empty states.
* Keys in each state must match GlEmptyState props
*/
states: { states: {
[dashboardEmptyStates.GETTING_STARTED]: { [dashboardEmptyStates.GETTING_STARTED]: {
svgUrl: this.emptyGettingStartedSvgPath, svgPath: this.emptyGettingStartedSvgPath,
title: __('Get started with performance monitoring'), title: __('Get started with performance monitoring'),
description: __(`Stay updated about the performance and health description: __(`Stay updated about the performance and health
of your environment by configuring Prometheus to monitor your deployments.`), of your environment by configuring Prometheus to monitor your deployments.`),
buttonText: __('Install on clusters'), primaryButtonText: __('Install on clusters'),
buttonPath: this.clustersPath, primaryButtonLink: this.clustersPath,
secondaryButtonText: __('Configure existing installation'), secondaryButtonText: __('Configure existing installation'),
secondaryButtonPath: this.settingsPath, secondaryButtonLink: this.settingsPath,
},
[dashboardEmptyStates.LOADING]: {
svgUrl: this.emptyLoadingSvgPath,
title: __('Waiting for performance data'),
description: __(`Creating graphs uses the data from the Prometheus server.
If this takes a long time, ensure that data is available.`),
buttonText: __('View documentation'),
buttonPath: this.documentationPath,
secondaryButtonText: '',
secondaryButtonPath: '',
}, },
[dashboardEmptyStates.NO_DATA]: { [dashboardEmptyStates.NO_DATA]: {
svgUrl: this.emptyNoDataSvgPath, svgPath: this.emptyNoDataSvgPath,
title: __('No data found'), title: __('No data found'),
description: __(`You are connected to the Prometheus server, but there is currently description: __(`You are connected to the Prometheus server, but there is currently
no data to display.`), no data to display.`),
buttonText: __('Configure Prometheus'), primaryButtonText: __('Configure Prometheus'),
buttonPath: this.settingsPath, primaryButtonLink: this.settingsPath,
secondaryButtonText: '', secondaryButtonText: '',
secondaryButtonPath: '', secondaryButtonLink: '',
}, },
[dashboardEmptyStates.UNABLE_TO_CONNECT]: { [dashboardEmptyStates.UNABLE_TO_CONNECT]: {
svgUrl: this.emptyUnableToConnectSvgPath, svgPath: this.emptyUnableToConnectSvgPath,
title: __('Unable to connect to Prometheus server'), title: __('Unable to connect to Prometheus server'),
description: __( description: __(
'Ensure connectivity is available from the GitLab server to the Prometheus server', 'Ensure connectivity is available from the GitLab server to the Prometheus server',
), ),
buttonText: __('View documentation'), primaryButtonText: __('View documentation'),
buttonPath: this.documentationPath, primaryButtonLink: this.documentationPath,
secondaryButtonText: __('Configure Prometheus'), secondaryButtonText: __('Configure Prometheus'),
secondaryButtonPath: this.settingsPath, secondaryButtonLink: this.settingsPath,
}, },
}, },
}; };
}, },
computed: { computed: {
isLoading() {
return this.selectedState === dashboardEmptyStates.LOADING;
},
currentState() { currentState() {
return this.states[this.selectedState]; return this.states[this.selectedState];
}, },
...@@ -108,14 +107,8 @@ export default { ...@@ -108,14 +107,8 @@ export default {
</script> </script>
<template> <template>
<gl-empty-state <div>
:title="currentState.title" <gl-loading-icon v-if="isLoading" size="xl" class="gl-my-9" />
:description="currentState.description" <gl-empty-state v-if="currentState" v-bind="currentState" :compact="compact" />
:primary-button-text="currentState.buttonText" </div>
:primary-button-link="currentState.buttonPath"
:secondary-button-text="currentState.secondaryButtonText"
:secondary-button-link="currentState.secondaryButtonPath"
:svg-path="currentState.svgUrl"
:compact="compact"
/>
</template> </template>
<script> <script>
import Icon from '~/vue_shared/components/icon.vue'; import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
export default { export default {
components: { components: {
Icon, GlLoadingIcon,
GlIcon,
}, },
props: { props: {
name: { name: {
...@@ -15,6 +16,11 @@ export default { ...@@ -15,6 +16,11 @@ export default {
required: false, required: false,
default: true, default: true,
}, },
isLoading: {
type: Boolean,
required: false,
default: false,
},
/** /**
* Initial value of collapse on mount. * Initial value of collapse on mount.
*/ */
...@@ -55,15 +61,18 @@ export default { ...@@ -55,15 +61,18 @@ export default {
<div v-if="showPanels" ref="graph-group" class="card prometheus-panel"> <div v-if="showPanels" ref="graph-group" class="card prometheus-panel">
<div class="card-header d-flex align-items-center"> <div class="card-header d-flex align-items-center">
<h4 class="flex-grow-1">{{ name }}</h4> <h4 class="flex-grow-1">{{ name }}</h4>
<gl-loading-icon v-if="isLoading" name="loading" />
<a <a
data-testid="group-toggle-button" data-testid="group-toggle-button"
:aria-label="__('Toggle collapse')"
:icon="caretIcon"
role="button" role="button"
class="js-graph-group-toggle gl-text-gray-900" class="js-graph-group-toggle gl-display-flex gl-ml-2 gl-text-gray-900"
tabindex="0" tabindex="0"
@click="collapse" @click="collapse"
@keyup.enter="collapse" @keyup.enter="collapse"
> >
<icon :size="16" :aria-label="__('Toggle collapse')" :name="caretIcon" /> <gl-icon :name="caretIcon" />
</a> </a>
</div> </div>
<div <div
......
...@@ -40,7 +40,6 @@ export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS'; ...@@ -40,7 +40,6 @@ export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
export const SET_ENDPOINTS = 'SET_ENDPOINTS'; export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_INITIAL_STATE = 'SET_INITIAL_STATE'; export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE'; export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER'; export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
export const SET_PANEL_GROUP_METRICS = 'SET_PANEL_GROUP_METRICS'; export const SET_PANEL_GROUP_METRICS = 'SET_PANEL_GROUP_METRICS';
export const SET_ENVIRONMENTS_FILTER = 'SET_ENVIRONMENTS_FILTER'; export const SET_ENVIRONMENTS_FILTER = 'SET_ENVIRONMENTS_FILTER';
......
...@@ -59,7 +59,6 @@ export default { ...@@ -59,7 +59,6 @@ export default {
*/ */
[types.REQUEST_METRICS_DASHBOARD](state) { [types.REQUEST_METRICS_DASHBOARD](state) {
state.emptyState = dashboardEmptyStates.LOADING; state.emptyState = dashboardEmptyStates.LOADING;
state.showEmptyState = true;
}, },
[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](state, dashboardYML) { [types.RECEIVE_METRICS_DASHBOARD_SUCCESS](state, dashboardYML) {
const { dashboard, panelGroups, variables, links } = mapToDashboardViewModel(dashboardYML); const { dashboard, panelGroups, variables, links } = mapToDashboardViewModel(dashboardYML);
...@@ -72,13 +71,14 @@ export default { ...@@ -72,13 +71,14 @@ export default {
if (!state.dashboard.panelGroups.length) { if (!state.dashboard.panelGroups.length) {
state.emptyState = dashboardEmptyStates.NO_DATA; state.emptyState = dashboardEmptyStates.NO_DATA;
} else {
state.emptyState = null;
} }
}, },
[types.RECEIVE_METRICS_DASHBOARD_FAILURE](state, error) { [types.RECEIVE_METRICS_DASHBOARD_FAILURE](state, error) {
state.emptyState = error state.emptyState = error
? dashboardEmptyStates.UNABLE_TO_CONNECT ? dashboardEmptyStates.UNABLE_TO_CONNECT
: dashboardEmptyStates.NO_DATA; : dashboardEmptyStates.NO_DATA;
state.showEmptyState = true;
}, },
[types.REQUEST_DASHBOARD_STARRING](state) { [types.REQUEST_DASHBOARD_STARRING](state) {
...@@ -152,9 +152,6 @@ export default { ...@@ -152,9 +152,6 @@ export default {
const metric = findMetricInDashboard(metricId, state.dashboard); const metric = findMetricInDashboard(metricId, state.dashboard);
metric.loading = false; metric.loading = false;
state.showEmptyState = false;
state.emptyState = null;
if (!data.result || data.result.length === 0) { if (!data.result || data.result.length === 0) {
metric.state = metricStates.NO_DATA; metric.state = metricStates.NO_DATA;
metric.result = null; metric.result = null;
...@@ -184,13 +181,8 @@ export default { ...@@ -184,13 +181,8 @@ export default {
state.timeRange = timeRange; state.timeRange = timeRange;
}, },
[types.SET_GETTING_STARTED_EMPTY_STATE](state) { [types.SET_GETTING_STARTED_EMPTY_STATE](state) {
state.showEmptyState = true;
state.emptyState = dashboardEmptyStates.GETTING_STARTED; state.emptyState = dashboardEmptyStates.GETTING_STARTED;
}, },
[types.SET_NO_DATA_EMPTY_STATE](state) {
state.showEmptyState = true;
state.emptyState = dashboardEmptyStates.NO_DATA;
},
[types.SET_ALL_DASHBOARDS](state, dashboards) { [types.SET_ALL_DASHBOARDS](state, dashboards) {
state.allDashboards = dashboards || []; state.allDashboards = dashboards || [];
}, },
......
...@@ -21,8 +21,13 @@ export default () => ({ ...@@ -21,8 +21,13 @@ export default () => ({
// Dashboard data // Dashboard data
hasDashboardValidationWarnings: false, hasDashboardValidationWarnings: false,
/**
* {?String} If set, dashboard should display a global
* empty state, there is no way to interact (yet)
* with the dashboard.
*/
emptyState: dashboardEmptyStates.GETTING_STARTED, emptyState: dashboardEmptyStates.GETTING_STARTED,
showEmptyState: true,
showErrorBanner: true, showErrorBanner: true,
isUpdatingStarredValue: false, isUpdatingStarredValue: false,
dashboard: { dashboard: {
......
...@@ -76,9 +76,11 @@ module Clusters ...@@ -76,9 +76,11 @@ module Clusters
'clusters-path': clusterable.index_path, 'clusters-path': clusterable.index_path,
'dashboard-endpoint': clusterable.metrics_dashboard_path(cluster), 'dashboard-endpoint': clusterable.metrics_dashboard_path(cluster),
'documentation-path': help_page_path('user/project/clusters/index', anchor: 'monitoring-your-kubernetes-cluster-ultimate'), 'documentation-path': help_page_path('user/project/clusters/index', anchor: 'monitoring-your-kubernetes-cluster-ultimate'),
'add-dashboard-documentation-path': help_page_path('user/project/integrations/prometheus.md', anchor: 'adding-a-new-dashboard-to-your-project'),
'empty-getting-started-svg-path': image_path('illustrations/monitoring/getting_started.svg'), 'empty-getting-started-svg-path': image_path('illustrations/monitoring/getting_started.svg'),
'empty-loading-svg-path': image_path('illustrations/monitoring/loading.svg'), 'empty-loading-svg-path': image_path('illustrations/monitoring/loading.svg'),
'empty-no-data-svg-path': image_path('illustrations/monitoring/no_data.svg'), 'empty-no-data-svg-path': image_path('illustrations/monitoring/no_data.svg'),
'empty-no-data-small-svg-path': image_path('illustrations/chart-empty-state-small.svg'),
'empty-unable-to-connect-svg-path': image_path('illustrations/monitoring/unable_to_connect.svg'), 'empty-unable-to-connect-svg-path': image_path('illustrations/monitoring/unable_to_connect.svg'),
'settings-path': '', 'settings-path': '',
'project-path': '', 'project-path': '',
......
---
title: Replace initial dashboard loading state with a loading spinner, show dashboard
skeleton earlier with smaller loading indicators
merge_request: 36399
author:
type: changed
...@@ -141,10 +141,6 @@ exports[`Dashboard template matches the default snapshot 1`] = ` ...@@ -141,10 +141,6 @@ exports[`Dashboard template matches the default snapshot 1`] = `
/> />
</div> </div>
<!---->
<!---->
<empty-state-stub <empty-state-stub
clusterspath="/monitoring/monitor-project/-/clusters" clusterspath="/monitoring/monitor-project/-/clusters"
documentationpath="/help/administration/monitoring/prometheus/index.md" documentationpath="/help/administration/monitoring/prometheus/index.md"
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EmptyState shows gettingStarted state 1`] = ` exports[`EmptyState shows gettingStarted state 1`] = `
<gl-empty-state-stub <div>
description="Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments." <!---->
primarybuttonlink="/clustersPath"
primarybuttontext="Install on clusters" <gl-empty-state-stub
secondarybuttonlink="/settingsPath" description="Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
secondarybuttontext="Configure existing installation" primarybuttonlink="/clustersPath"
svgpath="/path/to/getting-started.svg" primarybuttontext="Install on clusters"
title="Get started with performance monitoring" secondarybuttonlink="/settingsPath"
/> secondarybuttontext="Configure existing installation"
`; svgpath="/path/to/getting-started.svg"
title="Get started with performance monitoring"
exports[`EmptyState shows loading state 1`] = ` />
<gl-empty-state-stub </div>
description="Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available."
primarybuttonlink="/documentationPath"
primarybuttontext="View documentation"
secondarybuttonlink=""
secondarybuttontext=""
svgpath="/path/to/loading.svg"
title="Waiting for performance data"
/>
`; `;
exports[`EmptyState shows noData state 1`] = ` exports[`EmptyState shows noData state 1`] = `
<gl-empty-state-stub <div>
description="You are connected to the Prometheus server, but there is currently no data to display." <!---->
primarybuttonlink="/settingsPath"
primarybuttontext="Configure Prometheus" <gl-empty-state-stub
secondarybuttonlink="" description="You are connected to the Prometheus server, but there is currently no data to display."
secondarybuttontext="" primarybuttonlink="/settingsPath"
svgpath="/path/to/no-data.svg" primarybuttontext="Configure Prometheus"
title="No data found" secondarybuttonlink=""
/> secondarybuttontext=""
svgpath="/path/to/no-data.svg"
title="No data found"
/>
</div>
`; `;
exports[`EmptyState shows unableToConnect state 1`] = ` exports[`EmptyState shows unableToConnect state 1`] = `
<gl-empty-state-stub <div>
description="Ensure connectivity is available from the GitLab server to the Prometheus server" <!---->
primarybuttonlink="/documentationPath"
primarybuttontext="View documentation" <gl-empty-state-stub
secondarybuttonlink="/settingsPath" description="Ensure connectivity is available from the GitLab server to the Prometheus server"
secondarybuttontext="Configure Prometheus" primarybuttonlink="/documentationPath"
svgpath="/path/to/unable-to-connect.svg" primarybuttontext="View documentation"
title="Unable to connect to Prometheus server" secondarybuttonlink="/settingsPath"
/> secondarybuttontext="Configure Prometheus"
svgpath="/path/to/unable-to-connect.svg"
title="Unable to connect to Prometheus server"
/>
</div>
`; `;
...@@ -143,7 +143,7 @@ describe('Dashboard', () => { ...@@ -143,7 +143,7 @@ describe('Dashboard', () => {
setupStoreWithData(store); setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.showEmptyState).toEqual(false); expect(wrapper.vm.emptyState).toBeNull();
expect(wrapper.findAll('.prometheus-panel')).toHaveLength(0); expect(wrapper.findAll('.prometheus-panel')).toHaveLength(0);
}); });
}); });
...@@ -455,6 +455,33 @@ describe('Dashboard', () => { ...@@ -455,6 +455,33 @@ describe('Dashboard', () => {
}); });
}); });
describe('when all panels in the first group are loading', () => {
const findGroupAt = i => wrapper.findAll(GraphGroup).at(i);
beforeEach(() => {
setupStoreWithDashboard(store);
const { panels } = store.state.monitoringDashboard.dashboard.panelGroups[0];
panels.forEach(({ metrics }) => {
store.commit(`monitoringDashboard/${types.REQUEST_METRIC_RESULT}`, {
metricId: metrics[0].metricId,
});
});
createShallowWrapper();
return wrapper.vm.$nextTick();
});
it('a loading icon appears in the first group', () => {
expect(findGroupAt(0).props('isLoading')).toBe(true);
});
it('a loading icon does not appear in the second group', () => {
expect(findGroupAt(1).props('isLoading')).toBe(false);
});
});
describe('when all requests have been commited by the store', () => { describe('when all requests have been commited by the store', () => {
beforeEach(() => { beforeEach(() => {
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, { store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
...@@ -478,6 +505,16 @@ describe('Dashboard', () => { ...@@ -478,6 +505,16 @@ describe('Dashboard', () => {
}); });
}); });
it('it does not show loading icons in any group', () => {
setupStoreWithData(store);
wrapper.vm.$nextTick(() => {
wrapper.findAll(GraphGroup).wrappers.forEach(groupWrapper => {
expect(groupWrapper.props('isLoading')).toBe(false);
});
});
});
// Note: This test is not working, .active does not show the active environment // Note: This test is not working, .active does not show the active environment
// eslint-disable-next-line jest/no-disabled-tests // eslint-disable-next-line jest/no-disabled-tests
it.skip('renders the environments dropdown with a single active element', () => { it.skip('renders the environments dropdown with a single active element', () => {
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import { dashboardEmptyStates } from '~/monitoring/constants'; import { dashboardEmptyStates } from '~/monitoring/constants';
import EmptyState from '~/monitoring/components/empty_state.vue'; import EmptyState from '~/monitoring/components/empty_state.vue';
function createComponent(props) { function createComponent(props) {
return shallowMount(EmptyState, { return shallowMount(EmptyState, {
propsData: { propsData: {
...props,
settingsPath: '/settingsPath', settingsPath: '/settingsPath',
clustersPath: '/clustersPath', clustersPath: '/clustersPath',
documentationPath: '/documentationPath', documentationPath: '/documentationPath',
...@@ -14,22 +14,24 @@ function createComponent(props) { ...@@ -14,22 +14,24 @@ function createComponent(props) {
emptyNoDataSvgPath: '/path/to/no-data.svg', emptyNoDataSvgPath: '/path/to/no-data.svg',
emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg', emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg',
emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
...props,
}, },
}); });
} }
describe('EmptyState', () => { describe('EmptyState', () => {
it('shows gettingStarted state', () => { it('shows loading state with a loading icon', () => {
const wrapper = createComponent({ const wrapper = createComponent({
selectedState: dashboardEmptyStates.GETTING_STARTED, selectedState: dashboardEmptyStates.LOADING,
}); });
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.find(GlEmptyState).exists()).toBe(false);
}); });
it('shows loading state', () => { it('shows gettingStarted state', () => {
const wrapper = createComponent({ const wrapper = createComponent({
selectedState: dashboardEmptyStates.LOADING, selectedState: dashboardEmptyStates.GETTING_STARTED,
}); });
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import GraphGroup from '~/monitoring/components/graph_group.vue'; import GraphGroup from '~/monitoring/components/graph_group.vue';
import Icon from '~/vue_shared/components/icon.vue'; import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
describe('Graph group component', () => { describe('Graph group component', () => {
let wrapper; let wrapper;
const findGroup = () => wrapper.find({ ref: 'graph-group' }); const findGroup = () => wrapper.find({ ref: 'graph-group' });
const findContent = () => wrapper.find({ ref: 'graph-group-content' }); const findContent = () => wrapper.find({ ref: 'graph-group-content' });
const findCaretIcon = () => wrapper.find(Icon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findCaretIcon = () => wrapper.find(GlIcon);
const findToggleButton = () => wrapper.find('[data-testid="group-toggle-button"]'); const findToggleButton = () => wrapper.find('[data-testid="group-toggle-button"]');
const createComponent = propsData => { const createComponent = propsData => {
...@@ -28,15 +29,19 @@ describe('Graph group component', () => { ...@@ -28,15 +29,19 @@ describe('Graph group component', () => {
}); });
}); });
it('should not show a loading icon', () => {
expect(findLoadingIcon().exists()).toBe(false);
});
it('should show the angle-down caret icon', () => { it('should show the angle-down caret icon', () => {
expect(findContent().isVisible()).toBe(true); expect(findContent().isVisible()).toBe(true);
expect(findCaretIcon().props('name')).toBe('angle-down'); expect(findCaretIcon().props('name')).toBe('angle-down');
}); });
it('should show the angle-right caret icon when the user collapses the group', () => { it('should show the angle-right caret icon when the user collapses the group', () => {
wrapper.vm.collapse(); findToggleButton().trigger('click');
return wrapper.vm.$nextTick(() => { return wrapper.vm.$nextTick().then(() => {
expect(findContent().isVisible()).toBe(false); expect(findContent().isVisible()).toBe(false);
expect(findCaretIcon().props('name')).toBe('angle-right'); expect(findCaretIcon().props('name')).toBe('angle-right');
}); });
...@@ -53,77 +58,94 @@ describe('Graph group component', () => { ...@@ -53,77 +58,94 @@ describe('Graph group component', () => {
collapseGroup: true, collapseGroup: true,
}); });
return wrapper.vm.$nextTick(() => { return wrapper.vm.$nextTick().then(() => {
expect(findContent().isVisible()).toBe(true); expect(findContent().isVisible()).toBe(true);
expect(findCaretIcon().props('name')).toBe('angle-down'); expect(findCaretIcon().props('name')).toBe('angle-down');
}); });
}); });
});
describe('When group is collapsed', () => { describe('When group is collapsed', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
name: 'panel', name: 'panel',
collapseGroup: true, collapseGroup: true,
});
}); });
});
it('should show the angle-down caret icon when collapseGroup is true', () => { it('should show the angle-down caret icon when collapseGroup is true', () => {
expect(wrapper.vm.caretIcon).toBe('angle-right'); expect(findCaretIcon().props('name')).toBe('angle-right');
}); });
it('should show the angle-right caret icon when collapseGroup is false', () => { it('should show the angle-right caret icon when collapseGroup is false', () => {
wrapper.vm.collapse(); findToggleButton().trigger('click');
expect(wrapper.vm.caretIcon).toBe('angle-down'); return wrapper.vm.$nextTick().then(() => {
expect(findCaretIcon().props('name')).toBe('angle-down');
}); });
});
it('should call collapse the graph group content when enter is pressed on the caret icon', () => { it('should call collapse the graph group content when enter is pressed on the caret icon', () => {
const graphGroupContent = findContent(); const graphGroupContent = findContent();
const button = findToggleButton(); const button = findToggleButton();
button.trigger('keyup.enter'); button.trigger('keyup.enter');
expect(graphGroupContent.isVisible()).toBe(false); expect(graphGroupContent.isVisible()).toBe(false);
});
});
describe('When groups can not be collapsed', () => {
beforeEach(() => {
createComponent({
name: 'panel',
showPanels: false,
collapseGroup: false,
}); });
}); });
describe('When groups can not be collapsed', () => { it('should not have a container when showPanels is false', () => {
beforeEach(() => { expect(findGroup().exists()).toBe(false);
createComponent({ expect(findContent().exists()).toBe(true);
name: 'panel', });
showPanels: false, });
collapseGroup: false,
}); describe('When group is loading', () => {
beforeEach(() => {
createComponent({
name: 'panel',
isLoading: true,
}); });
});
it('should not have a container when showPanels is false', () => { it('should show a loading icon', () => {
expect(findGroup().exists()).toBe(false); expect(findLoadingIcon().exists()).toBe(true);
expect(findContent().exists()).toBe(true); });
});
describe('When group does not show a panel heading', () => {
beforeEach(() => {
createComponent({
name: 'panel',
showPanels: false,
collapseGroup: false,
}); });
}); });
describe('When group does not show a panel heading', () => { it('should collapse the panel content', () => {
beforeEach(() => { expect(findContent().isVisible()).toBe(true);
createComponent({ expect(findCaretIcon().exists()).toBe(false);
name: 'panel', });
showPanels: false,
collapseGroup: false, it('should show the panel content when collapse is set to false', () => {
}); wrapper.setProps({
collapseGroup: false,
}); });
it('should collapse the panel content', () => { return wrapper.vm.$nextTick().then(() => {
expect(findContent().isVisible()).toBe(true); expect(findContent().isVisible()).toBe(true);
expect(findCaretIcon().exists()).toBe(false); expect(findCaretIcon().exists()).toBe(false);
}); });
it('should show the panel content when clicked', () => {
wrapper.vm.collapse();
return wrapper.vm.$nextTick(() => {
expect(findContent().isVisible()).toBe(true);
expect(findCaretIcon().exists()).toBe(false);
});
});
}); });
}); });
}); });
...@@ -15,7 +15,7 @@ describe('Links Section component', () => { ...@@ -15,7 +15,7 @@ describe('Links Section component', () => {
const setState = links => { const setState = links => {
store.state.monitoringDashboard = { store.state.monitoringDashboard = {
...store.state.monitoringDashboard, ...store.state.monitoringDashboard,
showEmptyState: false, emptyState: null,
links, links,
}; };
}; };
......
...@@ -29,7 +29,7 @@ describe('Metrics dashboard/variables section component', () => { ...@@ -29,7 +29,7 @@ describe('Metrics dashboard/variables section component', () => {
beforeEach(() => { beforeEach(() => {
store = createStore(); store = createStore();
store.state.monitoringDashboard.showEmptyState = false; store.state.monitoringDashboard.emptyState = null;
}); });
it('does not show the variables section', () => { it('does not show the variables section', () => {
...@@ -70,7 +70,7 @@ describe('Metrics dashboard/variables section component', () => { ...@@ -70,7 +70,7 @@ describe('Metrics dashboard/variables section component', () => {
monitoringDashboard: { monitoringDashboard: {
namespaced: true, namespaced: true,
state: { state: {
showEmptyState: false, emptyState: null,
variables: storeVariables, variables: storeVariables,
}, },
actions: { actions: {
......
...@@ -20,7 +20,6 @@ describe('Monitoring mutations', () => { ...@@ -20,7 +20,6 @@ describe('Monitoring mutations', () => {
mutations[types.REQUEST_METRICS_DASHBOARD](stateCopy); mutations[types.REQUEST_METRICS_DASHBOARD](stateCopy);
expect(stateCopy.emptyState).toBe(dashboardEmptyStates.LOADING); expect(stateCopy.emptyState).toBe(dashboardEmptyStates.LOADING);
expect(stateCopy.showEmptyState).toBe(true);
}); });
}); });
...@@ -98,14 +97,12 @@ describe('Monitoring mutations', () => { ...@@ -98,14 +97,12 @@ describe('Monitoring mutations', () => {
mutations[types.RECEIVE_METRICS_DASHBOARD_FAILURE](stateCopy); mutations[types.RECEIVE_METRICS_DASHBOARD_FAILURE](stateCopy);
expect(stateCopy.emptyState).toBe(dashboardEmptyStates.NO_DATA); expect(stateCopy.emptyState).toBe(dashboardEmptyStates.NO_DATA);
expect(stateCopy.showEmptyState).toBe(true);
}); });
it('sets an empty unableToConnect state when an error occurs', () => { it('sets an empty unableToConnect state when an error occurs', () => {
mutations[types.RECEIVE_METRICS_DASHBOARD_FAILURE](stateCopy, 'myerror'); mutations[types.RECEIVE_METRICS_DASHBOARD_FAILURE](stateCopy, 'myerror');
expect(stateCopy.emptyState).toBe(dashboardEmptyStates.UNABLE_TO_CONNECT); expect(stateCopy.emptyState).toBe(dashboardEmptyStates.UNABLE_TO_CONNECT);
expect(stateCopy.showEmptyState).toBe(true);
}); });
}); });
...@@ -292,13 +289,10 @@ describe('Monitoring mutations', () => { ...@@ -292,13 +289,10 @@ describe('Monitoring mutations', () => {
mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, dashboard); mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, dashboard);
}); });
it('stores a loading state on a metric', () => { it('stores a loading state on a metric', () => {
expect(stateCopy.showEmptyState).toBe(true);
mutations[types.REQUEST_METRIC_RESULT](stateCopy, { mutations[types.REQUEST_METRIC_RESULT](stateCopy, {
metricId, metricId,
}); });
expect(stateCopy.showEmptyState).toBe(true);
expect(getMetric()).toEqual( expect(getMetric()).toEqual(
expect.objectContaining({ expect.objectContaining({
loading: true, loading: true,
...@@ -311,17 +305,6 @@ describe('Monitoring mutations', () => { ...@@ -311,17 +305,6 @@ describe('Monitoring mutations', () => {
beforeEach(() => { beforeEach(() => {
mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, dashboard); mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, dashboard);
}); });
it('clears empty state', () => {
expect(stateCopy.showEmptyState).toBe(true);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](stateCopy, {
metricId,
data,
});
expect(stateCopy.showEmptyState).toBe(false);
expect(stateCopy.emptyState).toBe(null);
});
it('adds results to the store', () => { it('adds results to the store', () => {
expect(getMetric().result).toBe(null); expect(getMetric().result).toBe(null);
...@@ -345,16 +328,6 @@ describe('Monitoring mutations', () => { ...@@ -345,16 +328,6 @@ describe('Monitoring mutations', () => {
beforeEach(() => { beforeEach(() => {
mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, dashboard); mutations[types.RECEIVE_METRICS_DASHBOARD_SUCCESS](stateCopy, dashboard);
}); });
it('maintains the loading state when a metric fails', () => {
expect(stateCopy.showEmptyState).toBe(true);
mutations[types.RECEIVE_METRIC_RESULT_FAILURE](stateCopy, {
metricId,
error: 'an error',
});
expect(stateCopy.showEmptyState).toBe(true);
});
it('stores a timeout error in a metric', () => { it('stores a timeout error in a metric', () => {
mutations[types.RECEIVE_METRIC_RESULT_FAILURE](stateCopy, { mutations[types.RECEIVE_METRIC_RESULT_FAILURE](stateCopy, {
......
...@@ -265,9 +265,11 @@ RSpec.describe Clusters::ClusterPresenter do ...@@ -265,9 +265,11 @@ RSpec.describe Clusters::ClusterPresenter do
is_expected.to include('clusters-path': clusterable_presenter.index_path, is_expected.to include('clusters-path': clusterable_presenter.index_path,
'dashboard-endpoint': clusterable_presenter.metrics_dashboard_path(cluster), 'dashboard-endpoint': clusterable_presenter.metrics_dashboard_path(cluster),
'documentation-path': help_page_path('user/project/clusters/index', anchor: 'monitoring-your-kubernetes-cluster-ultimate'), 'documentation-path': help_page_path('user/project/clusters/index', anchor: 'monitoring-your-kubernetes-cluster-ultimate'),
'add-dashboard-documentation-path': help_page_path('user/project/integrations/prometheus.md', anchor: 'adding-a-new-dashboard-to-your-project'),
'empty-getting-started-svg-path': match_asset_path('/assets/illustrations/monitoring/getting_started.svg'), 'empty-getting-started-svg-path': match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
'empty-loading-svg-path': match_asset_path('/assets/illustrations/monitoring/loading.svg'), 'empty-loading-svg-path': match_asset_path('/assets/illustrations/monitoring/loading.svg'),
'empty-no-data-svg-path': match_asset_path('/assets/illustrations/monitoring/no_data.svg'), 'empty-no-data-svg-path': match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
'empty-no-data-small-svg-path': match_asset_path('illustrations/chart-empty-state-small.svg'),
'empty-unable-to-connect-svg-path': match_asset_path('/assets/illustrations/monitoring/unable_to_connect.svg'), 'empty-unable-to-connect-svg-path': match_asset_path('/assets/illustrations/monitoring/unable_to_connect.svg'),
'settings-path': '', 'settings-path': '',
'project-path': '', 'project-path': '',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment