Commit a7190223 authored by Miguel Rincon's avatar Miguel Rincon

Display a star next to `starred` dashboards

When dashboards are fetched with a new `starred` property,
they are displayed with an icon indicating they have been starred.
parent 071e8dac
......@@ -2,6 +2,7 @@
import { mapState, mapActions } from 'vuex';
import {
GlAlert,
GlIcon,
GlDropdown,
GlDropdownItem,
GlDropdownHeader,
......@@ -21,6 +22,7 @@ const events = {
export default {
components: {
GlAlert,
GlIcon,
GlDropdown,
GlDropdownItem,
GlDropdownHeader,
......@@ -60,20 +62,31 @@ export default {
selectedDashboardText() {
return this.selectedDashboard.display_name;
},
filteredDashboards() {
return this.allDashboards.filter(({ display_name }) =>
return this.allDashboards.filter(({ display_name = '' }) =>
display_name.toLowerCase().includes(this.searchTerm.toLowerCase()),
);
},
shouldShowNoMsgContainer() {
return this.filteredDashboards.length === 0;
},
starredDashboards() {
return this.filteredDashboards.filter(({ starred }) => starred);
},
nonStarredDashboards() {
return this.filteredDashboards.filter(({ starred }) => !starred);
},
okButtonText() {
return this.loading ? s__('Metrics|Duplicating...') : s__('Metrics|Duplicate');
},
},
methods: {
...mapActions('monitoringDashboard', ['duplicateSystemDashboard']),
dashboardDisplayName(dashboard) {
return dashboard.display_name || dashboard.path || '';
},
selectDashboard(dashboard) {
this.$emit(events.selectDashboard, dashboard);
},
......@@ -127,15 +140,34 @@ export default {
v-model="searchTerm"
class="m-2"
/>
<div class="flex-fill overflow-auto">
<gl-dropdown-item
v-for="dashboard in filteredDashboards"
v-for="dashboard in starredDashboards"
:key="dashboard.path"
:active="dashboard.path === selectedDashboard.path"
active-class="is-active"
@click="selectDashboard(dashboard)"
>
<div class="d-flex">
{{ dashboardDisplayName(dashboard) }}
<gl-icon class="text-muted ml-auto" name="star" />
</div>
</gl-dropdown-item>
<gl-dropdown-divider
v-if="starredDashboards.length && nonStarredDashboards.length"
ref="starredListDivider"
/>
<gl-dropdown-item
v-for="dashboard in nonStarredDashboards"
:key="dashboard.path"
:active="dashboard.path === selectedDashboard.path"
active-class="is-active"
@click="selectDashboard(dashboard)"
>
{{ dashboard.display_name || dashboard.path }}
{{ dashboardDisplayName(dashboard) }}
</gl-dropdown-item>
</div>
......
import { shallowMount } from '@vue/test-utils';
import { GlDropdownItem, GlModal, GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { GlDropdownItem, GlModal, GlLoadingIcon, GlAlert, GlIcon } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
......@@ -9,36 +9,45 @@ import { dashboardGitResponse } from '../mock_data';
const defaultBranch = 'master';
function createComponent(props, opts = {}) {
const storeOpts = {
methods: {
duplicateSystemDashboard: jest.fn(),
},
computed: {
allDashboards: () => dashboardGitResponse,
},
};
return shallowMount(DashboardsDropdown, {
propsData: {
...props,
defaultBranch,
},
sync: false,
...storeOpts,
...opts,
});
}
const starredDashboards = dashboardGitResponse.filter(({ starred }) => starred);
const notStarredDashboards = dashboardGitResponse.filter(({ starred }) => !starred);
describe('DashboardsDropdown', () => {
let wrapper;
let mockDashboards;
function createComponent(props, opts = {}) {
const storeOpts = {
methods: {
duplicateSystemDashboard: jest.fn(),
},
computed: {
allDashboards: () => mockDashboards,
},
};
return shallowMount(DashboardsDropdown, {
propsData: {
...props,
defaultBranch,
},
sync: false,
...storeOpts,
...opts,
});
}
const findItems = () => wrapper.findAll(GlDropdownItem);
const findItemAt = i => wrapper.findAll(GlDropdownItem).at(i);
const findSearchInput = () => wrapper.find({ ref: 'monitorDashboardsDropdownSearch' });
const findNoItemsMsg = () => wrapper.find({ ref: 'monitorDashboardsDropdownMsg' });
const findStarredListDivider = () => wrapper.find({ ref: 'starredListDivider' });
const setSearchTerm = searchTerm => wrapper.setData({ searchTerm });
beforeEach(() => {
mockDashboards = dashboardGitResponse;
});
describe('when it receives dashboards data', () => {
beforeEach(() => {
wrapper = createComponent();
......@@ -48,10 +57,14 @@ describe('DashboardsDropdown', () => {
expect(findItems().length).toEqual(dashboardGitResponse.length);
});
it('displays items with the dashboard display name', () => {
expect(findItemAt(0).text()).toBe(dashboardGitResponse[0].display_name);
expect(findItemAt(1).text()).toBe(dashboardGitResponse[1].display_name);
expect(findItemAt(2).text()).toBe(dashboardGitResponse[2].display_name);
it('displays items with the dashboard display name, with starred dashboards first', () => {
expect(findItemAt(0).text()).toBe(starredDashboards[0].display_name);
expect(findItemAt(1).text()).toBe(notStarredDashboards[0].display_name);
expect(findItemAt(2).text()).toBe(notStarredDashboards[1].display_name);
});
it('displays separator between starred and not starred dashboards', () => {
expect(findStarredListDivider().exists()).toBe(true);
});
it('displays a search input', () => {
......@@ -81,6 +94,60 @@ describe('DashboardsDropdown', () => {
});
});
describe('when the dashboard is missing a display name', () => {
beforeEach(() => {
mockDashboards = dashboardGitResponse.map(d => ({ ...d, display_name: undefined }));
wrapper = createComponent();
});
it('displays items with the dashboard path, with starred dashboards first', () => {
expect(findItemAt(0).text()).toBe(starredDashboards[0].path);
expect(findItemAt(1).text()).toBe(notStarredDashboards[0].path);
expect(findItemAt(2).text()).toBe(notStarredDashboards[1].path);
});
});
describe('when it receives starred dashboards', () => {
beforeEach(() => {
mockDashboards = starredDashboards;
wrapper = createComponent();
});
it('displays an item for each dashboard', () => {
expect(findItems().length).toEqual(starredDashboards.length);
});
it('displays a star icon', () => {
const star = findItemAt(0).find(GlIcon);
expect(star.exists()).toBe(true);
expect(star.attributes('name')).toBe('star');
});
it('displays no separator between starred and not starred dashboards', () => {
expect(findStarredListDivider().exists()).toBe(false);
});
});
describe('when it receives only not-starred dashboards', () => {
beforeEach(() => {
mockDashboards = notStarredDashboards;
wrapper = createComponent();
});
it('displays an item for each dashboard', () => {
expect(findItems().length).toEqual(notStarredDashboards.length);
});
it('displays no star icon', () => {
const star = findItemAt(0).find(GlIcon);
expect(star.exists()).toBe(false);
});
it('displays no separator between starred and not starred dashboards', () => {
expect(findStarredListDivider().exists()).toBe(false);
});
});
describe('when a system dashboard is selected', () => {
let duplicateDashboardAction;
let modalDirective;
......@@ -260,7 +327,7 @@ describe('DashboardsDropdown', () => {
expect(wrapper.emitted().selectDashboard).toBeTruthy();
});
it('emits a "selectDashboard" event with dashboard information', () => {
expect(wrapper.emitted().selectDashboard[0]).toEqual([dashboardGitResponse[1]]);
expect(wrapper.emitted().selectDashboard[0]).toEqual([dashboardGitResponse[0]]);
});
});
});
......@@ -34,6 +34,7 @@ const customDashboardsData = new Array(30).fill(null).map((_, idx) => ({
system_dashboard: false,
project_blob_path: `${mockProjectDir}/blob/master/dashboards/.gitlab/dashboards/dashboard_${idx}.yml`,
path: `.gitlab/dashboards/dashboard_${idx}.yml`,
starred: false,
}));
export const mockDashboardsErrorResponse = {
......@@ -323,6 +324,16 @@ export const dashboardGitResponse = [
system_dashboard: true,
project_blob_path: null,
path: 'config/prometheus/common_metrics.yml',
starred: false,
},
{
default: false,
display_name: 'dashboard.yml',
can_edit: true,
system_dashboard: false,
project_blob_path: `${mockProjectDir}/-/blob/master/.gitlab/dashboards/dashboard.yml`,
path: '.gitlab/dashboards/dashboard.yml',
starred: true,
},
...customDashboardsData,
];
......
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