mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
dashfolders: convert folder settings to React
This commit is contained in:
parent
e1aff1d5ff
commit
545d7b9477
@ -102,8 +102,8 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dashboardChildNavs := []*dtos.NavLink{
|
dashboardChildNavs := []*dtos.NavLink{
|
||||||
{Text: "Home", Url: setting.AppSubUrl + "/", Icon: "gicon gicon-home", HideFromTabs: true},
|
{Text: "Home", Id: "home", Url: setting.AppSubUrl + "/", Icon: "gicon gicon-home", HideFromTabs: true},
|
||||||
{Divider: true, HideFromTabs: true},
|
{Text: "Divider", Divider: true, Id: "divider", HideFromTabs: true},
|
||||||
{Text: "Manage", Id: "manage-dashboards", Url: setting.AppSubUrl + "/dashboards", Icon: "gicon gicon-manage"},
|
{Text: "Manage", Id: "manage-dashboards", Url: setting.AppSubUrl + "/dashboards", Icon: "gicon gicon-manage"},
|
||||||
{Text: "Playlists", Id: "playlists", Url: setting.AppSubUrl + "/playlists", Icon: "gicon gicon-playlists"},
|
{Text: "Playlists", Id: "playlists", Url: setting.AppSubUrl + "/playlists", Icon: "gicon gicon-playlists"},
|
||||||
{Text: "Snapshots", Id: "snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots", Icon: "gicon gicon-snapshots"},
|
{Text: "Snapshots", Id: "snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots", Icon: "gicon gicon-snapshots"},
|
||||||
|
@ -3,6 +3,7 @@ import { ServerStatsStore } from './../stores/ServerStatsStore/ServerStatsStore'
|
|||||||
import { NavStore } from './../stores/NavStore/NavStore';
|
import { NavStore } from './../stores/NavStore/NavStore';
|
||||||
import { AlertListStore } from './../stores/AlertListStore/AlertListStore';
|
import { AlertListStore } from './../stores/AlertListStore/AlertListStore';
|
||||||
import { ViewStore } from './../stores/ViewStore/ViewStore';
|
import { ViewStore } from './../stores/ViewStore/ViewStore';
|
||||||
|
import { FolderStore } from './../stores/FolderStore/FolderStore';
|
||||||
|
|
||||||
interface IContainerProps {
|
interface IContainerProps {
|
||||||
search: typeof SearchStore.Type;
|
search: typeof SearchStore.Type;
|
||||||
@ -10,6 +11,7 @@ interface IContainerProps {
|
|||||||
nav: typeof NavStore.Type;
|
nav: typeof NavStore.Type;
|
||||||
alertList: typeof AlertListStore.Type;
|
alertList: typeof AlertListStore.Type;
|
||||||
view: typeof ViewStore.Type;
|
view: typeof ViewStore.Type;
|
||||||
|
folder: typeof FolderStore.Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IContainerProps;
|
export default IContainerProps;
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FolderSettings } from './FolderSettings';
|
||||||
|
import { RootStore } from 'app/stores/RootStore/RootStore';
|
||||||
|
import { backendSrv } from 'test/mocks/common';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
describe('FolderSettings', () => {
|
||||||
|
let wrapper;
|
||||||
|
let page;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
backendSrv.getDashboard.mockReturnValue(
|
||||||
|
Promise.resolve({
|
||||||
|
dashboard: {
|
||||||
|
id: 1,
|
||||||
|
title: 'Folder Name',
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
slug: 'folder-name',
|
||||||
|
canSave: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const store = RootStore.create(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
backendSrv: backendSrv,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper = shallow(<FolderSettings {...store} />);
|
||||||
|
return wrapper
|
||||||
|
.dive()
|
||||||
|
.instance()
|
||||||
|
.loadStore()
|
||||||
|
.then(() => {
|
||||||
|
page = wrapper.dive();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the title input field', () => {
|
||||||
|
const titleInput = page.find('.gf-form-input');
|
||||||
|
expect(titleInput).toHaveLength(1);
|
||||||
|
expect(titleInput.prop('value')).toBe('Folder Name');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update title and enable save button when changed', () => {
|
||||||
|
const titleInput = page.find('.gf-form-input');
|
||||||
|
const disabledSubmitButton = page.find('button[type="submit"]');
|
||||||
|
expect(disabledSubmitButton.prop('disabled')).toBe(true);
|
||||||
|
|
||||||
|
titleInput.simulate('change', { target: { value: 'New Title' } });
|
||||||
|
|
||||||
|
const updatedTitleInput = page.find('.gf-form-input');
|
||||||
|
expect(updatedTitleInput.prop('value')).toBe('New Title');
|
||||||
|
const enabledSubmitButton = page.find('button[type="submit"]');
|
||||||
|
expect(enabledSubmitButton.prop('disabled')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable save button if title is changed back to old title', () => {
|
||||||
|
const titleInput = page.find('.gf-form-input');
|
||||||
|
|
||||||
|
titleInput.simulate('change', { target: { value: 'Folder Name' } });
|
||||||
|
|
||||||
|
const enabledSubmitButton = page.find('button[type="submit"]');
|
||||||
|
expect(enabledSubmitButton.prop('disabled')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable save button if title is changed to empty string', () => {
|
||||||
|
const titleInput = page.find('.gf-form-input');
|
||||||
|
|
||||||
|
titleInput.simulate('change', { target: { value: '' } });
|
||||||
|
|
||||||
|
const enabledSubmitButton = page.find('button[type="submit"]');
|
||||||
|
expect(enabledSubmitButton.prop('disabled')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
153
public/app/containers/ManageDashboards/FolderSettings.tsx
Normal file
153
public/app/containers/ManageDashboards/FolderSettings.tsx
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { inject, observer } from 'mobx-react';
|
||||||
|
import { toJS } from 'mobx';
|
||||||
|
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
||||||
|
import IContainerProps from 'app/containers/IContainerProps';
|
||||||
|
import { getSnapshot } from 'mobx-state-tree';
|
||||||
|
import appEvents from 'app/core/app_events';
|
||||||
|
|
||||||
|
@inject('nav', 'folder', 'view')
|
||||||
|
@observer
|
||||||
|
export class FolderSettings extends React.Component<IContainerProps, any> {
|
||||||
|
formSnapshot: any;
|
||||||
|
dashboard: any;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.loadStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStore() {
|
||||||
|
const { nav, folder, view } = this.props;
|
||||||
|
|
||||||
|
return folder.load(view.routeParams.get('slug') as string).then(res => {
|
||||||
|
this.formSnapshot = getSnapshot(folder);
|
||||||
|
this.dashboard = res.dashboard;
|
||||||
|
|
||||||
|
return nav.initFolderNav(toJS(folder.folder), 'manage-folder-settings');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onTitleChange(evt) {
|
||||||
|
this.props.folder.setTitle(this.getFormSnapshot().folder.title, evt.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormSnapshot() {
|
||||||
|
if (!this.formSnapshot) {
|
||||||
|
this.formSnapshot = getSnapshot(this.props.folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.formSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
save(evt) {
|
||||||
|
if (evt) {
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { nav, folder, view } = this.props;
|
||||||
|
|
||||||
|
folder
|
||||||
|
.saveDashboard(this.dashboard, { overwrite: false })
|
||||||
|
.then(newUrl => {
|
||||||
|
view.updatePathAndQuery(newUrl, '', '');
|
||||||
|
|
||||||
|
appEvents.emit('dashboard-saved');
|
||||||
|
appEvents.emit('alert-success', ['Folder saved']);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return nav.initFolderNav(toJS(folder.folder), 'manage-folder-settings');
|
||||||
|
})
|
||||||
|
.catch(this.handleSaveFolderError);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(evt) {
|
||||||
|
if (evt) {
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { folder, view } = this.props;
|
||||||
|
const title = folder.folder.title;
|
||||||
|
|
||||||
|
appEvents.emit('confirm-modal', {
|
||||||
|
title: 'Delete',
|
||||||
|
text: `Do you want to delete this folder and all its dashboards?`,
|
||||||
|
icon: 'fa-trash',
|
||||||
|
yesText: 'Delete',
|
||||||
|
onConfirm: () => {
|
||||||
|
return this.props.folder.deleteFolder().then(() => {
|
||||||
|
appEvents.emit('alert-success', ['Folder Deleted', `${title} has been deleted`]);
|
||||||
|
view.updatePathAndQuery('dashboards', '', '');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSaveFolderError(err) {
|
||||||
|
if (err.data && err.data.status === 'version-mismatch') {
|
||||||
|
err.isHandled = true;
|
||||||
|
|
||||||
|
appEvents.emit('confirm-modal', {
|
||||||
|
title: 'Conflict',
|
||||||
|
text: 'Someone else has updated this folder.',
|
||||||
|
text2: 'Would you still like to save this folder?',
|
||||||
|
yesText: 'Save & Overwrite',
|
||||||
|
icon: 'fa-warning',
|
||||||
|
onConfirm: () => {
|
||||||
|
this.props.folder.saveDashboard(this.dashboard, { overwrite: true });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.data && err.data.status === 'name-exists') {
|
||||||
|
err.isHandled = true;
|
||||||
|
|
||||||
|
appEvents.emit('alert-error', ['A folder or dashboard with this name exists already.']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { nav, folder } = this.props;
|
||||||
|
|
||||||
|
if (!folder.folder || !nav.main) {
|
||||||
|
return <h2>Loading</h2>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageHeader model={nav as any} />
|
||||||
|
<div className="page-container page-body">
|
||||||
|
<h2 className="page-sub-heading">Folder Settings</h2>
|
||||||
|
|
||||||
|
<div className="section gf-form-group">
|
||||||
|
<form name="folderSettingsForm" onSubmit={this.save.bind(this)}>
|
||||||
|
<div className="gf-form">
|
||||||
|
<label className="gf-form-label width-7">Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="gf-form-input width-30"
|
||||||
|
value={folder.folder.title}
|
||||||
|
onChange={this.onTitleChange.bind(this)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="gf-form-button-row">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-success"
|
||||||
|
disabled={!folder.folder.canSave || !folder.folder.hasChanged}
|
||||||
|
>
|
||||||
|
<i className="fa fa-trash" /> Save
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-danger" onClick={this.delete.bind(this)} disabled={!folder.folder.canSave}>
|
||||||
|
<i className="fa fa-trash" /> Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -95,11 +95,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-results-container">
|
<div class="search-results-container">
|
||||||
<dashboard-search-results
|
<dashboard-search-results
|
||||||
results="ctrl.sections"
|
results="ctrl.sections"
|
||||||
editable="true"
|
editable="true"
|
||||||
on-selection-changed="ctrl.selectionChanged()"
|
on-selection-changed="ctrl.selectionChanged()"
|
||||||
on-tag-selected="ctrl.filterByTag($tag)" />
|
on-tag-selected="ctrl.filterByTag($tag)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,7 +10,7 @@ export class BridgeSrv {
|
|||||||
private fullPageReloadRoutes;
|
private fullPageReloadRoutes;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private $location, private $timeout, private $window, private $rootScope) {
|
constructor(private $location, private $timeout, private $window, private $rootScope, private $route) {
|
||||||
this.appSubUrl = config.appSubUrl;
|
this.appSubUrl = config.appSubUrl;
|
||||||
this.fullPageReloadRoutes = ['/logout'];
|
this.fullPageReloadRoutes = ['/logout'];
|
||||||
}
|
}
|
||||||
@ -29,14 +29,14 @@ export class BridgeSrv {
|
|||||||
this.$rootScope.$on('$routeUpdate', (evt, data) => {
|
this.$rootScope.$on('$routeUpdate', (evt, data) => {
|
||||||
let angularUrl = this.$location.url();
|
let angularUrl = this.$location.url();
|
||||||
if (store.view.currentUrl !== angularUrl) {
|
if (store.view.currentUrl !== angularUrl) {
|
||||||
store.view.updatePathAndQuery(this.$location.path(), this.$location.search());
|
store.view.updatePathAndQuery(this.$location.path(), this.$location.search(), this.$route.current.params);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$rootScope.$on('$routeChangeSuccess', (evt, data) => {
|
this.$rootScope.$on('$routeChangeSuccess', (evt, data) => {
|
||||||
let angularUrl = this.$location.url();
|
let angularUrl = this.$location.url();
|
||||||
if (store.view.currentUrl !== angularUrl) {
|
if (store.view.currentUrl !== angularUrl) {
|
||||||
store.view.updatePathAndQuery(this.$location.path(), this.$location.search());
|
store.view.updatePathAndQuery(this.$location.path(), this.$location.search(), this.$route.current.params);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ describe('BridgeSrv', () => {
|
|||||||
let searchSrv;
|
let searchSrv;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
searchSrv = new BridgeSrv(null, null, null, null);
|
searchSrv = new BridgeSrv(null, null, null, null, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('With /subUrl as appSubUrl', () => {
|
describe('With /subUrl as appSubUrl', () => {
|
||||||
|
@ -43,33 +43,33 @@ export class FolderPageLoader {
|
|||||||
ctrl.navModel.main.text = '';
|
ctrl.navModel.main.text = '';
|
||||||
ctrl.navModel.main.breadcrumbs = [{ title: 'Dashboards', url: 'dashboards' }, { title: folderTitle }];
|
ctrl.navModel.main.breadcrumbs = [{ title: 'Dashboards', url: 'dashboards' }, { title: folderTitle }];
|
||||||
|
|
||||||
const folderUrl = this.createFolderUrl(folderId, result.meta.type, result.meta.slug);
|
const folderUrl = this.createFolderUrl(folderId, result.meta.slug);
|
||||||
|
|
||||||
const dashTab = _.find(ctrl.navModel.main.children, {
|
const dashTab = _.find(ctrl.navModel.main.children, {
|
||||||
id: 'manage-folder-dashboards',
|
id: 'manage-folder-dashboards',
|
||||||
});
|
});
|
||||||
dashTab.url = folderUrl;
|
dashTab.url = folderUrl;
|
||||||
|
|
||||||
if (result.meta.canAdmin) {
|
if (result.meta.canAdmin) {
|
||||||
const permTab = _.find(ctrl.navModel.main.children, {
|
const permTab = _.find(ctrl.navModel.main.children, {
|
||||||
id: 'manage-folder-permissions',
|
id: 'manage-folder-permissions',
|
||||||
});
|
});
|
||||||
|
|
||||||
permTab.url = folderUrl + '/permissions';
|
permTab.url = folderUrl + '/permissions';
|
||||||
|
|
||||||
const settingsTab = _.find(ctrl.navModel.main.children, {
|
const settingsTab = _.find(ctrl.navModel.main.children, {
|
||||||
id: 'manage-folder-settings',
|
id: 'manage-folder-settings',
|
||||||
});
|
});
|
||||||
settingsTab.url = folderUrl + '/settings';
|
settingsTab.url = folderUrl + '/settings';
|
||||||
} else {
|
} else {
|
||||||
ctrl.navModel.main.children = [dashTab];
|
ctrl.navModel.main.children = [dashTab];
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createFolderUrl(folderId: number, type: string, slug: string) {
|
createFolderUrl(folderId: number, slug: string) {
|
||||||
return `dashboards/folder/${folderId}/${slug}`;
|
return `dashboards/folder/${folderId}/${slug}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ export class FolderSettingsCtrl {
|
|||||||
return this.backendSrv
|
return this.backendSrv
|
||||||
.saveDashboard(this.dashboard, { overwrite: false })
|
.saveDashboard(this.dashboard, { overwrite: false })
|
||||||
.then(result => {
|
.then(result => {
|
||||||
var folderUrl = this.folderPageLoader.createFolderUrl(this.folderId, this.meta.type, result.slug);
|
var folderUrl = this.folderPageLoader.createFolderUrl(this.folderId, result.slug);
|
||||||
if (folderUrl !== this.$location.path()) {
|
if (folderUrl !== this.$location.path()) {
|
||||||
this.$location.url(folderUrl + '/settings');
|
this.$location.url(folderUrl + '/settings');
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import './dashboard_loaders';
|
|||||||
import './ReactContainer';
|
import './ReactContainer';
|
||||||
import { ServerStats } from 'app/containers/ServerStats/ServerStats';
|
import { ServerStats } from 'app/containers/ServerStats/ServerStats';
|
||||||
import { AlertRuleList } from 'app/containers/AlertRuleList/AlertRuleList';
|
import { AlertRuleList } from 'app/containers/AlertRuleList/AlertRuleList';
|
||||||
|
import { FolderSettings } from 'app/containers/ManageDashboards/FolderSettings';
|
||||||
|
|
||||||
/** @ngInject **/
|
/** @ngInject **/
|
||||||
export function setupAngularRoutes($routeProvider, $locationProvider) {
|
export function setupAngularRoutes($routeProvider, $locationProvider) {
|
||||||
@ -68,9 +69,10 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
|
|||||||
controllerAs: 'ctrl',
|
controllerAs: 'ctrl',
|
||||||
})
|
})
|
||||||
.when('/dashboards/folder/:folderId/:slug/settings', {
|
.when('/dashboards/folder/:folderId/:slug/settings', {
|
||||||
templateUrl: 'public/app/features/dashboard/partials/folder_settings.html',
|
template: '<react-container />',
|
||||||
controller: 'FolderSettingsCtrl',
|
resolve: {
|
||||||
controllerAs: 'ctrl',
|
component: () => FolderSettings,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.when('/dashboards/folder/:folderId/:slug', {
|
.when('/dashboards/folder/:folderId/:slug', {
|
||||||
templateUrl: 'public/app/features/dashboard/partials/folder_dashboards.html',
|
templateUrl: 'public/app/features/dashboard/partials/folder_dashboards.html',
|
||||||
|
45
public/app/stores/FolderStore/FolderStore.ts
Normal file
45
public/app/stores/FolderStore/FolderStore.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { types, getEnv, flow } from 'mobx-state-tree';
|
||||||
|
|
||||||
|
export const Folder = types.model('Folder', {
|
||||||
|
id: types.identifier(types.number),
|
||||||
|
slug: types.string,
|
||||||
|
title: types.string,
|
||||||
|
canSave: types.boolean,
|
||||||
|
hasChanged: types.boolean,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const FolderStore = types
|
||||||
|
.model('FolderStore', {
|
||||||
|
folder: types.maybe(Folder),
|
||||||
|
})
|
||||||
|
.actions(self => ({
|
||||||
|
load: flow(function* load(slug: string) {
|
||||||
|
const backendSrv = getEnv(self).backendSrv;
|
||||||
|
const res = yield backendSrv.getDashboard('db', slug);
|
||||||
|
self.folder = Folder.create({
|
||||||
|
id: res.dashboard.id,
|
||||||
|
title: res.dashboard.title,
|
||||||
|
slug: res.meta.slug,
|
||||||
|
canSave: res.meta.canSave,
|
||||||
|
hasChanged: false,
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}),
|
||||||
|
setTitle: function(originalTitle: string, title: string) {
|
||||||
|
self.folder.title = title;
|
||||||
|
self.folder.hasChanged = originalTitle.toLowerCase() !== title.trim().toLowerCase() && title.trim().length > 0;
|
||||||
|
},
|
||||||
|
saveDashboard: flow(function* saveDashboard(dashboard: any, options: any) {
|
||||||
|
const backendSrv = getEnv(self).backendSrv;
|
||||||
|
dashboard.title = self.folder.title.trim();
|
||||||
|
|
||||||
|
const res = yield backendSrv.saveDashboard(dashboard, options);
|
||||||
|
self.folder.slug = res.slug;
|
||||||
|
return `dashboards/folder/${self.folder.id}/${res.slug}/settings`;
|
||||||
|
}),
|
||||||
|
deleteFolder: flow(function* deleteFolder() {
|
||||||
|
const backendSrv = getEnv(self).backendSrv;
|
||||||
|
|
||||||
|
return backendSrv.deleteDashboard(self.folder.slug);
|
||||||
|
}),
|
||||||
|
}));
|
47
public/app/stores/NavStore/NavStore.jest.ts
Normal file
47
public/app/stores/NavStore/NavStore.jest.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { NavStore } from './NavStore';
|
||||||
|
|
||||||
|
describe('NavStore', () => {
|
||||||
|
const folderId = 1;
|
||||||
|
const folderTitle = 'Folder Name';
|
||||||
|
const folderSlug = 'folder-name';
|
||||||
|
const canAdmin = true;
|
||||||
|
|
||||||
|
const folder = {
|
||||||
|
id: folderId,
|
||||||
|
slug: folderSlug,
|
||||||
|
title: folderTitle,
|
||||||
|
canAdmin: canAdmin,
|
||||||
|
};
|
||||||
|
|
||||||
|
let store;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = NavStore.create();
|
||||||
|
store.initFolderNav(folder, 'manage-folder-settings');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should set text', () => {
|
||||||
|
expect(store.main.text).toBe(folderTitle);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should load nav with tabs', () => {
|
||||||
|
expect(store.main.children.length).toBe(3);
|
||||||
|
expect(store.main.children[0].id).toBe('manage-folder-dashboards');
|
||||||
|
expect(store.main.children[1].id).toBe('manage-folder-permissions');
|
||||||
|
expect(store.main.children[2].id).toBe('manage-folder-settings');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should set correct urls for each tab', () => {
|
||||||
|
expect(store.main.children.length).toBe(3);
|
||||||
|
expect(store.main.children[0].url).toBe(`dashboards/folder/${folderId}/${folderSlug}`);
|
||||||
|
expect(store.main.children[1].url).toBe(`dashboards/folder/${folderId}/${folderSlug}/permissions`);
|
||||||
|
expect(store.main.children[2].url).toBe(`dashboards/folder/${folderId}/${folderSlug}/settings`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should set active tab', () => {
|
||||||
|
expect(store.main.children.length).toBe(3);
|
||||||
|
expect(store.main.children[0].active).toBe(false);
|
||||||
|
expect(store.main.children[1].active).toBe(false);
|
||||||
|
expect(store.main.children[2].active).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
@ -38,4 +38,43 @@ export const NavStore = types
|
|||||||
self.main = NavItem.create(main);
|
self.main = NavItem.create(main);
|
||||||
self.node = NavItem.create(node);
|
self.node = NavItem.create(node);
|
||||||
},
|
},
|
||||||
|
initFolderNav(folder: any, activeChildId: string) {
|
||||||
|
const folderUrl = createFolderUrl(folder.id, folder.slug);
|
||||||
|
|
||||||
|
self.main = {
|
||||||
|
icon: 'fa fa-folder-open',
|
||||||
|
id: 'manage-folder',
|
||||||
|
subTitle: 'Manage folder dashboards & permissions',
|
||||||
|
url: '',
|
||||||
|
text: folder.title,
|
||||||
|
breadcrumbs: [{ title: 'Dashboards', url: 'dashboards' }],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
active: activeChildId === 'manage-folder-dashboards',
|
||||||
|
icon: 'fa fa-fw fa-th-large',
|
||||||
|
id: 'manage-folder-dashboards',
|
||||||
|
text: 'Dashboards',
|
||||||
|
url: folderUrl,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: activeChildId === 'manage-folder-permissions',
|
||||||
|
icon: 'fa fa-fw fa-lock',
|
||||||
|
id: 'manage-folder-permissions',
|
||||||
|
text: 'Permissions',
|
||||||
|
url: folderUrl + '/permissions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: activeChildId === 'manage-folder-settings',
|
||||||
|
icon: 'fa fa-fw fa-cog',
|
||||||
|
id: 'manage-folder-settings',
|
||||||
|
text: 'Settings',
|
||||||
|
url: folderUrl + '/settings',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
function createFolderUrl(folderId: number, slug: string) {
|
||||||
|
return `dashboards/folder/${folderId}/${slug}`;
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { ServerStatsStore } from './../ServerStatsStore/ServerStatsStore';
|
|||||||
import { NavStore } from './../NavStore/NavStore';
|
import { NavStore } from './../NavStore/NavStore';
|
||||||
import { AlertListStore } from './../AlertListStore/AlertListStore';
|
import { AlertListStore } from './../AlertListStore/AlertListStore';
|
||||||
import { ViewStore } from './../ViewStore/ViewStore';
|
import { ViewStore } from './../ViewStore/ViewStore';
|
||||||
|
import { FolderStore } from './../FolderStore/FolderStore';
|
||||||
|
|
||||||
export const RootStore = types.model({
|
export const RootStore = types.model({
|
||||||
search: types.optional(SearchStore, {
|
search: types.optional(SearchStore, {
|
||||||
@ -19,7 +20,9 @@ export const RootStore = types.model({
|
|||||||
view: types.optional(ViewStore, {
|
view: types.optional(ViewStore, {
|
||||||
path: '',
|
path: '',
|
||||||
query: {},
|
query: {},
|
||||||
|
routeParams: {},
|
||||||
}),
|
}),
|
||||||
|
folder: types.optional(FolderStore, {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
type IRootStoreType = typeof RootStore.Type;
|
type IRootStoreType = typeof RootStore.Type;
|
||||||
|
@ -15,6 +15,7 @@ export const ViewStore = types
|
|||||||
.model({
|
.model({
|
||||||
path: types.string,
|
path: types.string,
|
||||||
query: types.map(QueryValueType),
|
query: types.map(QueryValueType),
|
||||||
|
routeParams: types.map(QueryValueType),
|
||||||
})
|
})
|
||||||
.views(self => ({
|
.views(self => ({
|
||||||
get currentUrl() {
|
get currentUrl() {
|
||||||
@ -34,9 +35,17 @@ export const ViewStore = types
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePathAndQuery(path: string, query: any) {
|
function updateRouteParams(routeParams: any) {
|
||||||
|
self.routeParams.clear();
|
||||||
|
for (let key of Object.keys(routeParams)) {
|
||||||
|
self.routeParams.set(key, routeParams[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePathAndQuery(path: string, query: any, routeParams: any) {
|
||||||
self.path = path;
|
self.path = path;
|
||||||
updateQuery(query);
|
updateQuery(query);
|
||||||
|
updateRouteParams(routeParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export const backendSrv = {
|
export const backendSrv = {
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
|
getDashboard: jest.fn(),
|
||||||
post: jest.fn(),
|
post: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -11,5 +12,6 @@ export function createNavTree(...args) {
|
|||||||
node.push(child);
|
node.push(child);
|
||||||
node = child.children;
|
node = child.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user