Commit 68101773 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '214581-star-dashboard-action' into 'master'

Add Vuex toggle action to star/unstar a dashboard

See merge request gitlab-org/gitlab!31580
parents 5007dbd2 f4bedbc1
......@@ -440,7 +440,6 @@ export default {
class="flex-grow-1"
toggle-class="dropdown-menu-toggle"
:default-branch="defaultBranch"
:selected-dashboard="selectedDashboard"
@selectDashboard="selectDashboard($event)"
/>
</div>
......
<script>
import { mapState, mapActions } from 'vuex';
import { mapState, mapActions, mapGetters } from 'vuex';
import {
GlAlert,
GlIcon,
......@@ -36,11 +36,6 @@ export default {
GlModal: GlModalDirective,
},
props: {
selectedDashboard: {
type: Object,
required: false,
default: () => ({}),
},
defaultBranch: {
type: String,
required: true,
......@@ -56,11 +51,15 @@ export default {
},
computed: {
...mapState('monitoringDashboard', ['allDashboards']),
...mapGetters('monitoringDashboard', ['selectedDashboard']),
isSystemDashboard() {
return this.selectedDashboard.system_dashboard;
return this.selectedDashboard?.system_dashboard;
},
selectedDashboardText() {
return this.selectedDashboard.display_name;
return this.selectedDashboard?.display_name;
},
selectedDashboardPath() {
return this.selectedDashboard?.path;
},
filteredDashboards() {
......@@ -145,7 +144,7 @@ export default {
<gl-dropdown-item
v-for="dashboard in starredDashboards"
:key="dashboard.path"
:active="dashboard.path === selectedDashboard.path"
:active="dashboard.path === selectedDashboardPath"
active-class="is-active"
@click="selectDashboard(dashboard)"
>
......@@ -163,7 +162,7 @@ export default {
<gl-dropdown-item
v-for="dashboard in nonStarredDashboards"
:key="dashboard.path"
:active="dashboard.path === selectedDashboard.path"
:active="dashboard.path === selectedDashboardPath"
active-class="is-active"
@click="selectDashboard(dashboard)"
>
......
......@@ -345,6 +345,35 @@ export const receiveAnnotationsFailure = ({ commit }) => commit(types.RECEIVE_AN
// Dashboard manipulation
export const toggleStarredValue = ({ commit, state, getters }) => {
const { selectedDashboard } = getters;
if (state.isUpdatingStarredValue) {
// Prevent repeating requests for the same change
return;
}
if (!selectedDashboard) {
return;
}
const method = selectedDashboard.starred ? 'DELETE' : 'POST';
const url = selectedDashboard.user_starred_path;
const newStarredValue = !selectedDashboard.starred;
commit(types.REQUEST_DASHBOARD_STARRING);
axios({
url,
method,
})
.then(() => {
commit(types.RECEIVE_DASHBOARD_STARRING_SUCCESS, newStarredValue);
})
.catch(() => {
commit(types.RECEIVE_DASHBOARD_STARRING_FAILURE);
});
};
/**
* Set a new array of metrics to a panel group
* @param {*} data An object containing
......
......@@ -3,6 +3,19 @@ import { NOT_IN_DB_PREFIX } from '../constants';
const metricsIdsInPanel = panel =>
panel.metrics.filter(metric => metric.metricId && metric.result).map(metric => metric.metricId);
/**
* Returns a reference to the currently selected dashboard
* from the list of dashboards.
*
* @param {Object} state
*/
export const selectedDashboard = state => {
const { allDashboards } = state;
return (
allDashboards.find(({ path }) => path === state.currentDashboard) || allDashboards[0] || null
);
};
/**
* Get all state for metric in the dashboard or a group. The
* states are not repeated so the dashboard or group can show
......
......@@ -5,6 +5,10 @@ export const RECEIVE_METRICS_DASHBOARD_FAILURE = 'RECEIVE_METRICS_DASHBOARD_FAIL
export const SET_PROM_QUERY_VARIABLES = 'SET_PROM_QUERY_VARIABLES';
export const UPDATE_VARIABLE_DATA = 'UPDATE_VARIABLE_DATA';
export const REQUEST_DASHBOARD_STARRING = 'REQUEST_DASHBOARD_STARRING';
export const RECEIVE_DASHBOARD_STARRING_SUCCESS = 'RECEIVE_DASHBOARD_STARRING_SUCCESS';
export const RECEIVE_DASHBOARD_STARRING_FAILURE = 'RECEIVE_DASHBOARD_STARRING_FAILURE';
// Annotations
export const RECEIVE_ANNOTATIONS_SUCCESS = 'RECEIVE_ANNOTATIONS_SUCCESS';
export const RECEIVE_ANNOTATIONS_FAILURE = 'RECEIVE_ANNOTATIONS_FAILURE';
......
import Vue from 'vue';
import { pick } from 'lodash';
import * as types from './mutation_types';
import { selectedDashboard } from './getters';
import { mapToDashboardViewModel, normalizeQueryResult } from './utils';
import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils';
import { endpointKeys, initialStateKeys, metricStates } from '../constants';
......@@ -71,6 +73,23 @@ export default {
state.showEmptyState = true;
},
[types.REQUEST_DASHBOARD_STARRING](state) {
state.isUpdatingStarredValue = true;
},
[types.RECEIVE_DASHBOARD_STARRING_SUCCESS](state, newStarredValue) {
const dashboard = selectedDashboard(state);
const index = state.allDashboards.findIndex(d => d === dashboard);
state.isUpdatingStarredValue = false;
// Trigger state updates in the reactivity system for this change
// https://vuejs.org/v2/guide/reactivity.html#For-Arrays
Vue.set(state.allDashboards, index, { ...dashboard, starred: newStarredValue });
},
[types.RECEIVE_DASHBOARD_STARRING_FAILURE](state) {
state.isUpdatingStarredValue = false;
},
/**
* Deployments and environments
*/
......
......@@ -14,6 +14,7 @@ export default () => ({
emptyState: 'gettingStarted',
showEmptyState: true,
showErrorBanner: true,
isUpdatingStarredValue: false,
dashboard: {
panelGroups: [],
},
......
......@@ -16,7 +16,6 @@ exports[`Dashboard template matches the default snapshot 1`] = `
data-qa-selector="dashboards_filter_dropdown"
defaultbranch="master"
id="monitor-dashboards-dropdown"
selecteddashboard="[object Object]"
toggle-class="dropdown-menu-toggle"
/>
</div>
......
......@@ -15,6 +15,7 @@ const notStarredDashboards = dashboardGitResponse.filter(({ starred }) => !starr
describe('DashboardsDropdown', () => {
let wrapper;
let mockDashboards;
let mockSelectedDashboard;
function createComponent(props, opts = {}) {
const storeOpts = {
......@@ -23,6 +24,7 @@ describe('DashboardsDropdown', () => {
},
computed: {
allDashboards: () => mockDashboards,
selectedDashboard: () => mockSelectedDashboard,
},
};
......@@ -46,6 +48,7 @@ describe('DashboardsDropdown', () => {
beforeEach(() => {
mockDashboards = dashboardGitResponse;
mockSelectedDashboard = null;
});
describe('when it receives dashboards data', () => {
......@@ -153,13 +156,12 @@ describe('DashboardsDropdown', () => {
let modalDirective;
beforeEach(() => {
[mockSelectedDashboard] = dashboardGitResponse;
modalDirective = jest.fn();
duplicateDashboardAction = jest.fn().mockResolvedValue();
wrapper = createComponent(
{
selectedDashboard: dashboardGitResponse[0],
},
{},
{
directives: {
GlModal: modalDirective,
......
......@@ -325,6 +325,7 @@ export const dashboardGitResponse = [
project_blob_path: null,
path: 'config/prometheus/common_metrics.yml',
starred: false,
user_starred_path: `${mockProjectDir}/metrics_user_starred_dashboards?dashboard_path=config/prometheus/common_metrics.yml`,
},
{
default: false,
......@@ -334,6 +335,7 @@ export const dashboardGitResponse = [
project_blob_path: `${mockProjectDir}/-/blob/master/.gitlab/dashboards/dashboard.yml`,
path: '.gitlab/dashboards/dashboard.yml',
starred: true,
user_starred_path: `${mockProjectDir}/metrics_user_starred_dashboards?dashboard_path=.gitlab/dashboards/dashboard.yml`,
},
...customDashboardsData,
];
......
......@@ -18,6 +18,7 @@ import {
fetchEnvironmentsData,
fetchDashboardData,
fetchAnnotations,
toggleStarredValue,
fetchPrometheusMetric,
setInitialState,
filterEnvironments,
......@@ -350,6 +351,49 @@ describe('Monitoring store actions', () => {
});
});
describe('Toggles starred value of current dashboard', () => {
const { state } = store;
let unstarredDashboard;
let starredDashboard;
beforeEach(() => {
state.isUpdatingStarredValue = false;
[unstarredDashboard, starredDashboard] = dashboardGitResponse;
});
describe('toggleStarredValue', () => {
it('performs no changes if no dashboard is selected', () => {
return testAction(toggleStarredValue, null, state, [], []);
});
it('performs no changes if already changing starred value', () => {
state.selectedDashboard = unstarredDashboard;
state.isUpdatingStarredValue = true;
return testAction(toggleStarredValue, null, state, [], []);
});
it('stars dashboard if it is not starred', () => {
state.selectedDashboard = unstarredDashboard;
mock.onPost(unstarredDashboard.user_starred_path).reply(200);
return testAction(toggleStarredValue, null, state, [
{ type: types.REQUEST_DASHBOARD_STARRING },
{ type: types.RECEIVE_DASHBOARD_STARRING_SUCCESS, payload: true },
]);
});
it('unstars dashboard if it is starred', () => {
state.selectedDashboard = starredDashboard;
mock.onPost(starredDashboard.user_starred_path).reply(200);
return testAction(toggleStarredValue, null, state, [
{ type: types.REQUEST_DASHBOARD_STARRING },
{ type: types.RECEIVE_DASHBOARD_STARRING_FAILURE },
]);
});
});
});
describe('Set initial state', () => {
let mockedState;
beforeEach(() => {
......
......@@ -3,7 +3,7 @@ import * as getters from '~/monitoring/stores/getters';
import mutations from '~/monitoring/stores/mutations';
import * as types from '~/monitoring/stores/mutation_types';
import { metricStates } from '~/monitoring/constants';
import { environmentData, metricsResult } from '../mock_data';
import { environmentData, metricsResult, dashboardGitResponse } from '../mock_data';
import {
metricsDashboardPayload,
metricResultStatus,
......@@ -350,4 +350,48 @@ describe('Monitoring store Getters', () => {
expect(variablesArray).toEqual([]);
});
});
describe('selectedDashboard', () => {
const { selectedDashboard } = getters;
it('returns a dashboard', () => {
const state = {
allDashboards: dashboardGitResponse,
currentDashboard: dashboardGitResponse[0].path,
};
expect(selectedDashboard(state)).toEqual(dashboardGitResponse[0]);
});
it('returns a non-default dashboard', () => {
const state = {
allDashboards: dashboardGitResponse,
currentDashboard: dashboardGitResponse[1].path,
};
expect(selectedDashboard(state)).toEqual(dashboardGitResponse[1]);
});
it('returns a default dashboard when no dashboard is selected', () => {
const state = {
allDashboards: dashboardGitResponse,
currentDashboard: null,
};
expect(selectedDashboard(state)).toEqual(dashboardGitResponse[0]);
});
it('returns a default dashboard when dashboard cannot be found', () => {
const state = {
allDashboards: dashboardGitResponse,
currentDashboard: 'wrong_path',
};
expect(selectedDashboard(state)).toEqual(dashboardGitResponse[0]);
});
it('returns null when no dashboards are present', () => {
const state = {
allDashboards: [],
currentDashboard: dashboardGitResponse[0].path,
};
expect(selectedDashboard(state)).toEqual(null);
});
});
});
......@@ -72,6 +72,49 @@ describe('Monitoring mutations', () => {
});
});
describe('Dashboard starring mutations', () => {
it('REQUEST_DASHBOARD_STARRING', () => {
stateCopy = { isUpdatingStarredValue: false };
mutations[types.REQUEST_DASHBOARD_STARRING](stateCopy);
expect(stateCopy.isUpdatingStarredValue).toBe(true);
});
describe('RECEIVE_DASHBOARD_STARRING_SUCCESS', () => {
let allDashboards;
beforeEach(() => {
allDashboards = [...dashboardGitResponse];
stateCopy = {
allDashboards,
currentDashboard: allDashboards[1].path,
isUpdatingStarredValue: true,
};
});
it('sets a dashboard as starred', () => {
mutations[types.RECEIVE_DASHBOARD_STARRING_SUCCESS](stateCopy, true);
expect(stateCopy.isUpdatingStarredValue).toBe(false);
expect(stateCopy.allDashboards[1].starred).toBe(true);
});
it('sets a dashboard as unstarred', () => {
mutations[types.RECEIVE_DASHBOARD_STARRING_SUCCESS](stateCopy, false);
expect(stateCopy.isUpdatingStarredValue).toBe(false);
expect(stateCopy.allDashboards[1].starred).toBe(false);
});
});
it('RECEIVE_DASHBOARD_STARRING_FAILURE', () => {
stateCopy = { isUpdatingStarredValue: true };
mutations[types.RECEIVE_DASHBOARD_STARRING_FAILURE](stateCopy);
expect(stateCopy.isUpdatingStarredValue).toBe(false);
});
});
describe('RECEIVE_DEPLOYMENTS_DATA_SUCCESS', () => {
it('stores the deployment data', () => {
stateCopy.deploymentData = [];
......
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