Commit 50e00489 authored by Miguel Rincon's avatar Miguel Rincon

Merge branch 'astoicescu/actions_menu_update' into 'master'

Add more actions to and change UI of dashboard actions menu

See merge request gitlab-org/gitlab!38946
parents 8269fe74 21c48641
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import {
GlDeprecatedButton,
GlNewDropdown,
GlNewDropdownDivider,
GlNewDropdownItem,
GlModal,
GlIcon,
GlModalDirective,
GlTooltipDirective,
} from '@gitlab/ui';
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
import DuplicateDashboardModal from './duplicate_dashboard_modal.vue';
import CreateDashboardModal from './create_dashboard_modal.vue';
import { s__ } from '~/locale';
import invalidUrl from '~/lib/utils/invalid_url';
import { redirectTo } from '~/lib/utils/url_utility';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import { getAddMetricTrackingOptions } from '../utils';
export default {
components: {
GlDeprecatedButton,
GlNewDropdown,
GlNewDropdownDivider,
GlNewDropdownItem,
GlModal,
GlIcon,
DuplicateDashboardModal,
CreateDashboardModal,
CustomMetricsFormFields,
},
directives: {
GlModal: GlModalDirective,
GlTooltip: GlTooltipDirective,
TrackEvent: TrackEventDirective,
},
props: {
addingMetricsAvailable: {
type: Boolean,
required: false,
default: false,
},
customMetricsPath: {
type: String,
required: false,
default: invalidUrl,
},
validateQueryPath: {
type: String,
required: false,
default: invalidUrl,
},
defaultBranch: {
type: String,
required: true,
},
},
data() {
return { customMetricsFormIsValid: null };
},
computed: {
...mapState('monitoringDashboard', [
'projectPath',
'isUpdatingStarredValue',
'addDashboardDocumentationPath',
]),
...mapGetters('monitoringDashboard', ['selectedDashboard']),
isOutOfTheBoxDashboard() {
return this.selectedDashboard?.out_of_the_box_dashboard;
},
isMenuItemEnabled() {
return {
createDashboard: Boolean(this.projectPath),
editDashboard: this.selectedDashboard?.can_edit,
};
},
isMenuItemShown() {
return {
duplicateDashboard: this.isOutOfTheBoxDashboard,
};
},
},
methods: {
...mapActions('monitoringDashboard', ['toggleStarredValue']),
setFormValidity(isValid) {
this.customMetricsFormIsValid = isValid;
},
hideAddMetricModal() {
this.$refs.addMetricModal.hide();
},
getAddMetricTrackingOptions,
submitCustomMetricsForm() {
this.$refs.customMetricsForm.submit();
},
selectDashboard(dashboard) {
// Once the sidebar See metrics link is updated to the new URL,
// this sort of hardcoding will not be necessary.
// https://gitlab.com/gitlab-org/gitlab/-/issues/229277
const baseURL = `${this.projectPath}/-/metrics`;
const dashboardPath = encodeURIComponent(
dashboard.out_of_the_box_dashboard ? dashboard.path : dashboard.display_name,
);
redirectTo(`${baseURL}/${dashboardPath}`);
},
},
modalIds: {
addMetric: 'addMetric',
createDashboard: 'createDashboard',
duplicateDashboard: 'duplicateDashboard',
},
i18n: {
actionsMenu: s__('Metrics|More actions'),
duplicateDashboard: s__('Metrics|Duplicate current dashboard'),
starDashboard: s__('Metrics|Star dashboard'),
unstarDashboard: s__('Metrics|Unstar dashboard'),
addMetric: s__('Metrics|Add metric'),
editDashboardInfo: s__('Metrics|Duplicate this dashboard to edit dashboard YAML'),
editDashboard: s__('Metrics|Edit dashboard YAML'),
createDashboard: s__('Metrics|Create new dashboard'),
},
};
</script>
<template>
<gl-new-dropdown
v-gl-tooltip
data-testid="actions-menu"
data-qa-selector="actions_menu_dropdown"
right
no-caret
toggle-class="gl-px-3!"
:title="$options.i18n.actionsMenu"
>
<template #button-content>
<gl-icon class="gl-mr-0!" name="ellipsis_v" />
</template>
<template v-if="addingMetricsAvailable">
<gl-new-dropdown-item
v-gl-modal="$options.modalIds.addMetric"
data-qa-selector="add_metric_button"
data-testid="add-metric-item"
>
{{ $options.i18n.addMetric }}
</gl-new-dropdown-item>
<gl-modal
ref="addMetricModal"
:modal-id="$options.modalIds.addMetric"
:title="$options.i18n.addMetric"
data-testid="add-metric-modal"
>
<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-deprecated-button @click="hideAddMetricModal">
{{ __('Cancel') }}
</gl-deprecated-button>
<gl-deprecated-button
v-track-event="getAddMetricTrackingOptions()"
data-testid="add-metric-modal-submit-button"
:disabled="!customMetricsFormIsValid"
variant="success"
@click="submitCustomMetricsForm"
>
{{ __('Save changes') }}
</gl-deprecated-button>
</div>
</gl-modal>
</template>
<gl-new-dropdown-item
v-if="isMenuItemEnabled.editDashboard"
:href="selectedDashboard ? selectedDashboard.project_blob_path : null"
data-qa-selector="edit_dashboard_button_enabled"
data-testid="edit-dashboard-item-enabled"
>
{{ $options.i18n.editDashboard }}
</gl-new-dropdown-item>
<!--
wrapper for tooltip as button can be `disabled`
https://bootstrap-vue.org/docs/components/tooltip#disabled-elements
-->
<div v-else v-gl-tooltip :title="$options.i18n.editDashboardInfo">
<gl-new-dropdown-item
:alt="$options.i18n.editDashboardInfo"
:href="selectedDashboard ? selectedDashboard.project_blob_path : null"
data-testid="edit-dashboard-item-disabled"
disabled
class="gl-cursor-not-allowed"
>
<span class="gl-text-gray-400">{{ $options.i18n.editDashboard }}</span>
</gl-new-dropdown-item>
</div>
<template v-if="isMenuItemShown.duplicateDashboard">
<gl-new-dropdown-item
v-gl-modal="$options.modalIds.duplicateDashboard"
data-testid="duplicate-dashboard-item"
>
{{ $options.i18n.duplicateDashboard }}
</gl-new-dropdown-item>
<duplicate-dashboard-modal
:default-branch="defaultBranch"
:modal-id="$options.modalIds.duplicateDashboard"
data-testid="duplicate-dashboard-modal"
@dashboardDuplicated="selectDashboard"
/>
</template>
<gl-new-dropdown-item
v-if="selectedDashboard"
data-testid="star-dashboard-item"
:disabled="isUpdatingStarredValue"
@click="toggleStarredValue()"
>
{{ selectedDashboard.starred ? $options.i18n.unstarDashboard : $options.i18n.starDashboard }}
</gl-new-dropdown-item>
<gl-new-dropdown-divider />
<gl-new-dropdown-item
v-gl-modal="$options.modalIds.createDashboard"
data-testid="create-dashboard-item"
:disabled="!isMenuItemEnabled.createDashboard"
:class="{ 'monitoring-actions-item-disabled': !isMenuItemEnabled.createDashboard }"
>
{{ $options.i18n.createDashboard }}
</gl-new-dropdown-item>
<template v-if="isMenuItemEnabled.createDashboard">
<create-dashboard-modal
data-testid="create-dashboard-modal"
:add-dashboard-documentation-path="addDashboardDocumentationPath"
:modal-id="$options.modalIds.createDashboard"
:project-path="projectPath"
/>
</template>
</gl-new-dropdown>
</template>
---
title: Change UI and add new actions to monitor dashboard actions menu
merge_request: 38946
author:
type: changed
......@@ -20,7 +20,7 @@ The metrics as defined below do not support alerts, unlike
## Add a new dashboard to your project
> UI option [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223204) in GitLab 13.2.
> UI option [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/228856) in GitLab 13.3.
You can configure a custom dashboard by adding a new YAML file into your project's
`.gitlab/dashboards/` directory. For the dashboard to display on your project's **Operations > Metrics** page, the files must have a `.yml`
......@@ -31,9 +31,9 @@ To create a new dashboard from the GitLab user interface:
1. Sign in to GitLab as a user with Maintainer or Owner
[permissions](../../../user/permissions.md#project-members-permissions).
1. Navigate to your dashboard at **Operations > Metrics**.
1. In the top-right corner of your dashboard, click the **{file-addition-solid}** **Actions** menu,
1. In the top-right corner of your dashboard, click the **{{ellipsis_v}}** **More actions** menu,
and select **Create new**:
![Monitoring Dashboard actions menu with create new item](img/actions_menu_create_new_dashboard_v13_2.png)
![Monitoring Dashboard actions menu with create new item](img/actions_menu_create_new_dashboard_v13_3.png)
1. In the modal window, click **Open Repository**, then follow the instructions
for creating a new dashboard from the command line.
......@@ -82,7 +82,7 @@ The resulting `.yml` file can be customized and adapted to your project.
You can decide to save the dashboard `.yml` file in the project's **default** branch or in a
new branch.
1. Click **Duplicate dashboard** in the actions menu.
1. Click **Duplicate current dashboard** in the **{{ellipsis_v}}** **More actions** menu.
NOTE: **Note:**
You can duplicate only GitLab-defined dashboards.
......@@ -105,7 +105,7 @@ To manage the settings for your metrics dashboard:
1. Navigate to your dashboard at **Operations > Metrics**.
1. In the top-right corner of your dashboard, click **Metrics Settings**:
![Monitoring Dashboard actions menu with create new item](img/metrics_settings_button_v13_2.png)
![Monitoring Dashboard actions menu with create new item](img/metrics_settings_button_v13_3.png)
## Chart Context Menu
......
......@@ -25,7 +25,7 @@ To view the metrics dashboard for an environment that has
GitLab displays the default metrics dashboard for the environment, like the
following example:
![Example of metrics dashboard](img/example-dashboard_v13_1.png)
![Example of metrics dashboard](img/example-dashboard_v13_3.png)
The top of the dashboard contains a navigation bar. From left to right, the
navigation bar contains:
......@@ -37,15 +37,19 @@ navigation bar contains:
- **Range** - The time period of data to display.
- **Refresh dashboard** **{retry}** - Reload the dashboard with current data.
- **Set refresh rate** - Set a time frame for refreshing the data displayed.
- **Star dashboard** **{star-o}** - Click to mark a dashboard as a favorite.
- **More actions** **{ellipsis_v}** - More dashboard actions
- **Add metric** - Adds a [custom metric](#adding-custom-metrics). Only available on GitLab-defined dashboards.
([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34779) in GitLab 12.5.)
- **Edit dashboard YAML** - Edit the source YAML file of a custom dashboard. Only available on
[custom dashboards](dashboards/index.md).
([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34779) in GitLab 12.5.)
- **Duplicate current dashboard** - Save a [complete copy of a dashboard](dashboards/index.md#duplicate-a-gitlab-defined-dashboard). Only available on GitLab-defined dashboards.
- **Star dashboard** **{star-o}** - Click to mark a dashboard as a favorite.
Starred dashboards display a solid star **{star}** button, and display first
in the **Dashboard** dropdown list.
([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214582) in GitLab 13.0.)
- **Edit dashboard** - Edit the source YAML file of a custom dashboard. Only available on
[custom dashboards](dashboards/index.md).
([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34779) in GitLab 12.5.)
- **Create dashboard** **{file-addition-solid}** - Create a
[new custom dashboard for your project](dashboards/index.md#add-a-new-dashboard-to-your-project).
- **Create new dashboard** - Create a [new custom dashboard for your project](dashboards/index.md#add-a-new-dashboard-to-your-project).
([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/228856) in GitLab 13.3.)
- **Metrics settings** - Configure the
[settings for this dashboard](dashboards/index.md#manage-the-metrics-dashboard-settings).
......@@ -70,7 +74,7 @@ helps quickly create a deployment:
1. When the pipeline has run successfully, graphs are available on the
**Operations > Metrics** page.
![Monitoring Dashboard](img/prometheus_monitoring_dashboard_v13_1.png)
![Monitoring Dashboard](img/prometheus_monitoring_dashboard_v13_3.png)
## Customize your metrics dashboard
......
......@@ -8804,9 +8804,6 @@ msgstr ""
msgid "Edit comment"
msgstr ""
msgid "Edit dashboard"
msgstr ""
msgid "Edit description"
msgstr ""
......@@ -15236,9 +15233,6 @@ msgstr ""
msgid "Metrics|Create custom dashboard %{fileName}"
msgstr ""
msgid "Metrics|Create dashboard"
msgstr ""
msgid "Metrics|Create metric"
msgstr ""
......@@ -15272,9 +15266,15 @@ msgstr ""
msgid "Metrics|Duplicate dashboard"
msgstr ""
msgid "Metrics|Duplicate this dashboard to edit dashboard YAML"
msgstr ""
msgid "Metrics|Duplicating..."
msgstr ""
msgid "Metrics|Edit dashboard YAML"
msgstr ""
msgid "Metrics|Edit metric"
msgid_plural "Metrics|Edit metrics"
msgstr[0] ""
......@@ -15316,6 +15316,9 @@ msgstr ""
msgid "Metrics|Min"
msgstr ""
msgid "Metrics|More actions"
msgstr ""
msgid "Metrics|Must be a valid PromQL query."
msgstr ""
......
......@@ -18,9 +18,12 @@ module QA
view 'app/assets/javascripts/monitoring/components/dashboard_header.vue' do
element :dashboards_filter_dropdown
element :environments_dropdown
element :edit_dashboard_button
element :range_picker_dropdown
end
view 'app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue' do
element :actions_menu_dropdown
element :edit_dashboard_button_enabled
end
view 'app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue' do
......@@ -56,7 +59,7 @@ module QA
def has_edit_dashboard_enabled?
within_element :prometheus_graphs do
has_element? :edit_dashboard_button
has_element? :edit_dashboard_button_enabled
end
end
......
......@@ -99,34 +99,19 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<div
class="d-sm-flex"
>
<div
class="mb-2 mr-2 d-flex"
>
<div
class="flex-grow-1"
title="Star dashboard"
>
<gl-button-stub
category="tertiary"
class="w-100"
icon="star-o"
size="medium"
variant="default"
/>
</div>
</div>
<!---->
<!---->
<!---->
<!---->
<!---->
<!---->
<div
class="gl-mb-3 gl-mr-3 d-flex d-sm-block"
>
<actions-menu-stub
custommetricspath="/monitoring/monitor-project/prometheus/metrics"
defaultbranch="master"
validatequerypath="/monitoring/monitor-project/prometheus/metrics/validate_query"
/>
</div>
<!---->
</div>
......
import { shallowMount, mount } from '@vue/test-utils';
import Tracking from '~/tracking';
import { ESC_KEY, ESC_KEY_IE11 } from '~/lib/utils/keys';
import { GlModal } from '@gitlab/ui';
import { objectToQuery } from '~/lib/utils/url_utility';
import VueDraggable from 'vuedraggable';
import MockAdapter from 'axios-mock-adapter';
......@@ -10,7 +8,6 @@ import { dashboardEmptyStates, metricStates } from '~/monitoring/constants';
import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
import EmptyState from '~/monitoring/components/empty_state.vue';
import GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
......@@ -42,8 +39,6 @@ describe('Dashboard', () => {
let wrapper;
let mock;
const findDashboardHeader = () => wrapper.find(DashboardHeader);
const createShallowWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(Dashboard, {
propsData: { ...dashboardProps, ...props },
......@@ -446,84 +441,6 @@ describe('Dashboard', () => {
});
});
describe('star dashboards', () => {
const findToggleStar = () => findDashboardHeader().find({ ref: 'toggleStarBtn' });
beforeEach(() => {
createShallowWrapper();
setupAllDashboards(store);
});
it('toggle star button is shown', () => {
expect(findToggleStar().exists()).toBe(true);
expect(findToggleStar().props('disabled')).toBe(false);
});
it('toggle star button is disabled when starring is taking place', () => {
store.commit(`monitoringDashboard/${types.REQUEST_DASHBOARD_STARRING}`);
return wrapper.vm.$nextTick(() => {
expect(findToggleStar().exists()).toBe(true);
expect(findToggleStar().props('disabled')).toBe(true);
});
});
describe('when the dashboard list is loaded', () => {
// Tooltip element should wrap directly
const getToggleTooltip = () => findToggleStar().element.parentElement.getAttribute('title');
beforeEach(() => {
setupAllDashboards(store);
jest.spyOn(store, 'dispatch');
});
it('dispatches a toggle star action', () => {
findToggleStar().vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(store.dispatch).toHaveBeenCalledWith(
'monitoringDashboard/toggleStarredValue',
undefined,
);
});
});
describe('when dashboard is not starred', () => {
beforeEach(() => {
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
currentDashboard: dashboardGitResponse[0].path,
});
return wrapper.vm.$nextTick();
});
it('toggle star button shows "Star dashboard"', () => {
expect(getToggleTooltip()).toBe('Star dashboard');
});
it('toggle star button shows an unstarred state', () => {
expect(findToggleStar().attributes('icon')).toBe('star-o');
});
});
describe('when dashboard is starred', () => {
beforeEach(() => {
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
currentDashboard: dashboardGitResponse[1].path,
});
return wrapper.vm.$nextTick();
});
it('toggle star button shows "Star dashboard"', () => {
expect(getToggleTooltip()).toBe('Unstar dashboard');
});
it('toggle star button shows a starred state', () => {
expect(findToggleStar().attributes('icon')).toBe('star');
});
});
});
});
describe('variables section', () => {
beforeEach(() => {
createShallowWrapper({ hasMetrics: true });
......@@ -800,33 +717,6 @@ describe('Dashboard', () => {
});
});
describe('dashboard edit link', () => {
const findEditLink = () => wrapper.find('.js-edit-link');
beforeEach(() => {
createShallowWrapper({ hasMetrics: true });
setupAllDashboards(store);
return wrapper.vm.$nextTick();
});
it('is not present for the overview dashboard', () => {
expect(findEditLink().exists()).toBe(false);
});
it('is present for a custom dashboard, and links to its edit_path', () => {
const dashboard = dashboardGitResponse[1];
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
currentDashboard: dashboard.path,
});
return wrapper.vm.$nextTick().then(() => {
expect(findEditLink().exists()).toBe(true);
expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path);
});
});
});
describe('document title', () => {
const originalTitle = 'Original Title';
const overviewDashboardName = dashboardGitResponse[0].display_name;
......@@ -940,74 +830,4 @@ describe('Dashboard', () => {
expect(dashboardPanel.exists()).toBe(true);
});
});
describe('add custom metrics', () => {
const findAddMetricButton = () => findDashboardHeader().find({ ref: 'addMetricBtn' });
describe('when not available', () => {
beforeEach(() => {
createShallowWrapper({
hasMetrics: true,
customMetricsPath: '/endpoint',
});
});
it('does not render add button on the dashboard', () => {
expect(findAddMetricButton().exists()).toBe(false);
});
});
describe('when available', () => {
let origPage;
beforeEach(done => {
jest.spyOn(Tracking, 'event').mockReturnValue();
createShallowWrapper({
hasMetrics: true,
customMetricsPath: '/endpoint',
customMetricsAvailable: true,
});
setupStoreWithData(store);
origPage = document.body.dataset.page;
document.body.dataset.page = 'projects:environments:metrics';
wrapper.vm.$nextTick(done);
});
afterEach(() => {
document.body.dataset.page = origPage;
});
it('renders add button on the dashboard', () => {
expect(findAddMetricButton()).toBeDefined();
});
it('uses modal for custom metrics form', () => {
expect(wrapper.find(GlModal).exists()).toBe(true);
expect(wrapper.find(GlModal).attributes().modalid).toBe('addMetric');
});
it('adding new metric is tracked', done => {
const submitButton = wrapper
.find(DashboardHeader)
.find({ ref: 'submitCustomMetricsFormBtn' }).vm;
wrapper.vm.$nextTick(() => {
submitButton.$el.click();
wrapper.vm.$nextTick(() => {
expect(Tracking.event).toHaveBeenCalledWith(
document.body.dataset.page,
'click_button',
{
label: 'add_new_metric',
property: 'modal',
value: undefined,
},
);
done();
});
});
});
it('renders custom metrics form fields', () => {
expect(wrapper.find(CustomMetricsFormFields).exists()).toBe(true);
});
});
});
});
......@@ -622,3 +622,10 @@ export const dashboardHeaderProps = {
end: '2020-01-01T01:00:00.000Z',
},
};
export const dashboardActionsMenuProps = {
defaultBranch: 'master',
addingMetricsAvailable: true,
customMetricsPath: 'https://path/to/customMetrics',
validateQueryPath: 'https://path/to/validateQuery',
};
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