wip: More on the permissions. Left are team picker and user picker, tests and error messages #10275

This commit is contained in:
Johannes Schill
2018-01-15 21:12:46 +01:00
committed by Daniel Lee
parent 460cbe98a1
commit 44ea8e58e2
5 changed files with 174 additions and 187 deletions

View File

@@ -5,6 +5,7 @@ import DevTools from 'mobx-react-devtools';
import { inject, observer } from 'mobx-react'; import { inject, observer } from 'mobx-react';
import { Provider } from 'mobx-react'; import { Provider } from 'mobx-react';
import { store } from 'app/stores/store'; import { store } from 'app/stores/store';
import UserPicker from 'app/core/components/UserPicker/UserPicker';
export interface DashboardAcl { export interface DashboardAcl {
id?: number; id?: number;
@@ -75,140 +76,42 @@ class PermissionsInner extends Component<IProps, any> {
this.permissionChanged = this.permissionChanged.bind(this); this.permissionChanged = this.permissionChanged.bind(this);
this.typeChanged = this.typeChanged.bind(this); this.typeChanged = this.typeChanged.bind(this);
this.removeItem = this.removeItem.bind(this); this.removeItem = this.removeItem.bind(this);
this.update = this.update.bind(this);
permissions.load(this.dashboardId); permissions.load(this.dashboardId);
this.state = { this.state = {
newType: 'Group', newType: 'Group',
canUpdate: false,
error: '',
}; };
} }
componentWillReceiveProps(nextProps) {
console.log('nextProps', nextProps);
}
sortItems(items) { sortItems(items) {
return _.orderBy(items, ['sortRank', 'sortName'], ['desc', 'asc']); return _.orderBy(items, ['sortRank', 'sortName'], ['desc', 'asc']);
} }
permissionChanged(evt) { permissionChanged(index: number, permission: number, permissionName: string) {
// TODO const { permissions } = this.props;
// permissions.items[index].updatePermission(permission, permissionName);
permissions.updatePermissionOnIndex(index, permission, permissionName);
} }
removeItem(index) { removeItem(index: number) {
const { permissions } = this.props; const { permissions } = this.props;
permissions.removeStoreItem(index); permissions.removeStoreItem(index);
} }
update() { update() {
var updated = []; const { permissions, dashboardId } = this.props;
for (let item of this.state.items) { permissions.update(dashboardId);
if (item.inherited) {
continue;
}
updated.push({
id: item.id,
userId: item.userId,
teamId: item.teamId,
role: item.role,
permission: item.permission,
});
}
return this.backendSrv
.post(`/api/dashboards/id/${this.dashboardId}/acl`, {
items: updated,
})
.then(() => {
this.setState(prevState => {
return {
...prevState,
canUpdate: false,
};
});
});
}
prepareViewModel(item: DashboardAcl): DashboardAcl {
// TODO: this.meta
// item.inherited = !this.meta.isFolder && this.dashboardId !== item.dashboardId;
item.inherited = this.dashboardId !== item.dashboardId;
item.sortRank = 0;
if (item.userId > 0) {
item.icon = 'fa fa-fw fa-user';
// item.nameHtml = this.$sce.trustAsHtml(item.userLogin);
item.nameHtml = item.userLogin;
item.sortName = item.userLogin;
item.sortRank = 10;
} else if (item.teamId > 0) {
item.icon = 'fa fa-fw fa-users';
// item.nameHtml = this.$sce.trustAsHtml(item.team);
item.nameHtml = item.team;
item.sortName = item.team;
item.sortRank = 20;
} else if (item.role) {
item.icon = 'fa fa-fw fa-street-view';
// item.nameHtml = this.$sce.trustAsHtml(`Everyone with <span class="query-keyword">${item.role}</span> Role`);
item.nameHtml = `Everyone with <span class="query-keyword">${item.role}</span> Role`;
item.sortName = item.role;
item.sortRank = 30;
if (item.role === 'Viewer') {
item.sortRank += 1;
}
}
if (item.inherited) {
item.sortRank += 100;
}
return item;
}
isDuplicate(origItem, newItem) {
if (origItem.inherited) {
return false;
}
return (
(origItem.role && newItem.role && origItem.role === newItem.role) ||
(origItem.userId && newItem.userId && origItem.userId === newItem.userId) ||
(origItem.teamId && newItem.teamId && origItem.teamId === newItem.teamId)
);
}
isValid(item) {
const dupe = _.find(this.items, it => {
return this.isDuplicate(it, item);
});
if (dupe) {
this.error = this.duplicateError;
return false;
}
return true;
}
addNewItem(item) {
if (!this.isValid(item)) {
return;
}
this.error = '';
item.dashboardId = this.dashboardId;
let newItems = this.state.items;
newItems.push(this.prepareViewModel(item));
this.setState(prevState => {
return {
...prevState,
items: this.sortItems(newItems),
canUpdate: true,
};
});
} }
resetNewType() { resetNewType() {
this.setState(prevState => { this.setState(prevState => {
return { return {
...prevState,
newType: 'Group', newType: 'Group',
}; };
}); });
@@ -216,6 +119,14 @@ class PermissionsInner extends Component<IProps, any> {
typeChanged(evt) { typeChanged(evt) {
const { value } = evt.target; const { value } = evt.target;
const { permissions } = this.props;
if (value === 'Viewer' || value === 'Editor') {
permissions.addStoreItem({ permission: 1, role: value, dashboardId: this.dashboardId }, this.dashboardId);
this.resetNewType();
return;
}
this.setState(prevState => { this.setState(prevState => {
return { return {
...prevState, ...prevState,
@@ -224,40 +135,27 @@ class PermissionsInner extends Component<IProps, any> {
}); });
} }
typeChanged___() {
const { newType } = this.state;
if (newType === 'Viewer' || newType === 'Editor') {
this.addNewItem({ permission: 1, role: newType });
this.resetNewType();
this.setState(prevState => {
return {
...prevState,
canUpdate: true,
};
});
}
}
render() { render() {
const { error, aclTypes, permissions } = this.props; console.log('PermissionsInner render');
const { error, aclTypes, permissions, backendSrv } = this.props;
const { newType } = this.state; const { newType } = this.state;
return ( return (
<div className="gf-form-group"> <div className="gf-form-group">
<PermissionsList <PermissionsList
permissions={permissions.items.toJS()} permissions={permissions.items}
permissionsOptions={this.permissionOptions} permissionsOptions={this.permissionOptions}
removeItem={this.removeItem} removeItem={this.removeItem}
permissionChanged={this.permissionChanged} permissionChanged={this.permissionChanged}
fetching={permissions.fetching}
/> />
asd2
<div className="gf-form-inline"> <div className="gf-form-inline">
<form name="addPermission" className="gf-form-group"> <form name="addPermission" className="gf-form-group">
<h6 className="muted">Add Permission For</h6> <h6 className="muted">Add Permission For</h6>
<div className="gf-form-inline"> <div className="gf-form-inline">
<div className="gf-form"> <div className="gf-form">
<div className="gf-form-select-wrapper"> <div className="gf-form-select-wrapper">
<select className="gf-form-input gf-size-auto" onChange={this.typeChanged}> <select className="gf-form-input gf-size-auto" value={newType} onChange={this.typeChanged}>
{aclTypes.map((option, idx) => { {aclTypes.map((option, idx) => {
return ( return (
<option key={idx} value={option.value}> <option key={idx} value={option.value}>
@@ -280,6 +178,13 @@ class PermissionsInner extends Component<IProps, any> {
{' '} {' '}
User picker User picker
<user-picker user-picked="ctrl.userPicked($user)" /> <user-picker user-picked="ctrl.userPicked($user)" />
<select-user-picker
backendSrv="ctrl.backendSrv"
teamId="ctrl.$routeParams.id"
refreshList="ctrl.get"
teamMembers="ctrl.teamMembers"
/>
<UserPicker backendSrv={backendSrv} teamId={0} />
</div> </div>
) : null} ) : null}
@@ -306,7 +211,6 @@ class PermissionsInner extends Component<IProps, any> {
Update Permissions Update Permissions
</button> </button>
</div> </div>
asd3
<DevTools /> <DevTools />
</div> </div>
); );

View File

@@ -1,23 +1,27 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PermissionsListItem from './PermissionsListItem'; import PermissionsListItem from './PermissionsListItem';
import { observer } from 'mobx-react';
export interface IProps { export interface IProps {
permissions: any[]; permissions: any[];
permissionsOptions: any[]; permissionsOptions: any[];
removeItem: any; removeItem: any;
permissionChanged: any; permissionChanged: any;
fetching: boolean;
} }
@observer
class PermissionsList extends Component<IProps, any> { class PermissionsList extends Component<IProps, any> {
render() { render() {
const { permissions, permissionsOptions, removeItem, permissionChanged } = this.props; const { permissions, permissionsOptions, removeItem, permissionChanged, fetching } = this.props;
return ( return (
<table className="filter-table gf-form-group"> <table className="filter-table gf-form-group">
<tbody> <tbody>
{permissions.map((item, idx) => { {permissions.map((item, idx) => {
return ( return (
<PermissionsListItem <PermissionsListItem
key={item.id} key={idx}
item={item} item={item}
itemIndex={idx} itemIndex={idx}
permissionsOptions={permissionsOptions} permissionsOptions={permissionsOptions}
@@ -55,7 +59,15 @@ class PermissionsList extends Component<IProps, any> {
<em>No permissions are set. Will only be accessible by admins.</em> <em>No permissions are set. Will only be accessible by admins.</em>
</td> </td>
</tr> */} </tr> */}
{permissions.length < 1 ? ( {fetching === true && permissions.length < 1 ? (
<tr>
<td colSpan={4}>
<em>Loading permissions...</em>
</td>
</tr>
) : null}
{fetching === false && permissions.length < 1 ? (
<tr> <tr>
<td colSpan={4}> <td colSpan={4}>
<em>No permissions are set. Will only be accessible by admins.</em> <em>No permissions are set. Will only be accessible by admins.</em>

View File

@@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react';
const setClassNameHelper = inherited => { const setClassNameHelper = inherited => {
return inherited ? 'gf-form-disabled' : ''; return inherited ? 'gf-form-disabled' : '';
}; };
export default ({ item, permissionsOptions, removeItem, permissionChanged, itemIndex }) => { export default observer(({ item, permissionsOptions, removeItem, permissionChanged, itemIndex }) => {
const handleRemoveItem = evt => { const handleRemoveItem = evt => {
evt.preventDefault(); evt.preventDefault();
removeItem(itemIndex); removeItem(itemIndex);
@@ -12,7 +13,10 @@ export default ({ item, permissionsOptions, removeItem, permissionChanged, itemI
const handleChangePermission = evt => { const handleChangePermission = evt => {
evt.preventDefault(); evt.preventDefault();
permissionChanged(itemIndex, evt.target.value); const value = evt.target.value;
const valueAsInt = parseInt(value, 10);
const newPermission = permissionsOptions.find(opt => opt.value === valueAsInt);
permissionChanged(itemIndex, newPermission.value, newPermission.text);
}; };
return ( return (
@@ -57,4 +61,4 @@ export default ({ item, permissionsOptions, removeItem, permissionChanged, itemI
</td> </td>
</tr> </tr>
); );
}; });

View File

@@ -1,6 +1,8 @@
import { types, getEnv, flow } from 'mobx-state-tree'; import { types, getEnv, flow } from 'mobx-state-tree';
import { PermissionsStoreItem } from './PermissionsStoreItem'; import { PermissionsStoreItem } from './PermissionsStoreItem';
const duplicateError = 'This permission exists already.';
export const PermissionsStore = types export const PermissionsStore = types
.model('PermissionsStore', { .model('PermissionsStore', {
fetching: types.boolean, fetching: types.boolean,
@@ -8,67 +10,128 @@ export const PermissionsStore = types
items: types.optional(types.array(PermissionsStoreItem), []), items: types.optional(types.array(PermissionsStoreItem), []),
originalItems: types.optional(types.array(PermissionsStoreItem), []), originalItems: types.optional(types.array(PermissionsStoreItem), []),
}) })
// .views(self => ({ .views(self => ({
// canUpdate: () => { isValid: item => {
// const itemsSnapshot = getSnapshot(self.items); const dupe = self.items.find(it => {
// const originalItemsSnapshot = getSnapshot(self.originalItems); return isDuplicate(it, item);
// console.log('itemsSnapshot', itemsSnapshot); });
// console.log('editItemsSnapshot', originalItemsSnapshot);
// return true; if (dupe) {
// } this.error = duplicateError;
// })) return false;
}
return true;
},
}))
.actions(self => ({ .actions(self => ({
load: flow(function* load(dashboardId: number) { load: flow(function* load(dashboardId: number) {
self.fetching = true;
const backendSrv = getEnv(self).backendSrv; const backendSrv = getEnv(self).backendSrv;
self.fetching = true;
const res = yield backendSrv.get(`/api/dashboards/id/${dashboardId}/acl`); const res = yield backendSrv.get(`/api/dashboards/id/${dashboardId}/acl`);
const items = prepareServerResponse(res, dashboardId); const items = prepareServerResponse(res, dashboardId);
self.items = items; self.items = items;
self.originalItems = items; self.originalItems = items;
self.fetching = false; self.fetching = false;
}), }),
addStoreItem: () => { addStoreItem: (item, dashboardId: number) => {
if (!self.isValid(item)) {
return;
}
self.items.push(prepareItem(item, dashboardId));
self.canUpdate = true; self.canUpdate = true;
}, },
removeStoreItem: idx => { removeStoreItem: idx => {
self.items.splice(idx, 1); self.items.splice(idx, 1);
self.canUpdate = true; self.canUpdate = true;
}, },
updatePermissionOnIndex(idx: number, permission: number, permissionName: string) {
// self.items[idx].permission = permission;
// self.items[idx].permissionName = permissionName;
self.items[idx].updatePermission(permission, permissionName);
self.canUpdate = true;
},
// load: flow(function* load(dashboardId: number) {
update: flow(function* update(dashboardId: number) {
const backendSrv = getEnv(self).backendSrv;
const updated = [];
for (let item of self.items) {
if (item.inherited) {
continue;
}
updated.push({
id: item.id,
userId: item.userId,
teamId: item.teamId,
role: item.role,
permission: item.permission,
});
}
let res;
try {
res = backendSrv.post(`/api/dashboards/id/${dashboardId}/acl`, {
items: updated,
});
} catch (error) {
console.error(error);
}
self.canUpdate = false;
return res;
}),
})); }));
const prepareServerResponse = (response, dashboardId: number) => { const prepareServerResponse = (response, dashboardId: number) => {
return response.map(item => { return response.map(item => {
// TODO: this.meta return prepareItem(item, dashboardId);
// item.inherited = !this.meta.isFolder && this.dashboardId !== item.dashboardId;
item.inherited = dashboardId !== item.dashboardId;
item.sortRank = 0;
if (item.userId > 0) {
item.icon = 'fa fa-fw fa-user';
// item.nameHtml = this.$sce.trustAsHtml(item.userLogin);
item.nameHtml = item.userLogin;
item.sortName = item.userLogin;
item.sortRank = 10;
} else if (item.teamId > 0) {
item.icon = 'fa fa-fw fa-users';
// item.nameHtml = this.$sce.trustAsHtml(item.team);
item.nameHtml = item.team;
item.sortName = item.team;
item.sortRank = 20;
} else if (item.role) {
item.icon = 'fa fa-fw fa-street-view';
// item.nameHtml = this.$sce.trustAsHtml(`Everyone with <span class="query-keyword">${item.role}</span> Role`);
item.nameHtml = `Everyone with <span class="query-keyword">${item.role}</span> Role`;
item.sortName = item.role;
item.sortRank = 30;
if (item.role === 'Viewer') {
item.sortRank += 1;
}
}
if (item.inherited) {
item.sortRank += 100;
}
return item;
}); });
}; };
const prepareItem = (item, dashboardId: number) => {
// TODO: this.meta
// item.inherited = !this.meta.isFolder && this.dashboardId !== item.dashboardId;
item.inherited = dashboardId !== item.dashboardId;
item.sortRank = 0;
if (item.userId > 0) {
item.icon = 'fa fa-fw fa-user';
// item.nameHtml = this.$sce.trustAsHtml(item.userLogin);
item.nameHtml = item.userLogin;
item.sortName = item.userLogin;
item.sortRank = 10;
} else if (item.teamId > 0) {
item.icon = 'fa fa-fw fa-users';
// item.nameHtml = this.$sce.trustAsHtml(item.team);
item.nameHtml = item.team;
item.sortName = item.team;
item.sortRank = 20;
} else if (item.role) {
item.icon = 'fa fa-fw fa-street-view';
// item.nameHtml = this.$sce.trustAsHtml(`Everyone with <span class="query-keyword">${item.role}</span> Role`);
item.nameHtml = `Everyone with <span class="query-keyword">${item.role}</span> Role`;
item.sortName = item.role;
item.sortRank = 30;
if (item.role === 'Viewer') {
item.sortRank += 1;
}
}
if (item.inherited) {
item.sortRank += 100;
}
return item;
};
const isDuplicate = (origItem, newItem) => {
if (origItem.inherited) {
return false;
}
return (
(origItem.role && newItem.role && origItem.role === newItem.role) ||
(origItem.userId && newItem.userId && origItem.userId === newItem.userId) ||
(origItem.teamId && newItem.teamId && origItem.teamId === newItem.teamId)
);
};

View File

@@ -2,16 +2,16 @@
export const PermissionsStoreItem = types export const PermissionsStoreItem = types
.model('PermissionsStoreItem', { .model('PermissionsStoreItem', {
dashboardId: types.number, dashboardId: types.optional(types.number, -1),
id: types.number, id: types.maybe(types.number),
permission: types.number, permission: types.number,
permissionName: types.string, permissionName: types.maybe(types.string),
role: types.maybe(types.string), role: types.maybe(types.string),
team: types.string, team: types.optional(types.string, ''),
teamId: types.number, teamId: types.optional(types.number, 0),
userEmail: types.string, userEmail: types.optional(types.string, ''),
userId: types.number, userId: types.optional(types.number, 0),
userLogin: types.string, userLogin: types.optional(types.string, ''),
inherited: types.maybe(types.boolean), inherited: types.maybe(types.boolean),
sortRank: types.maybe(types.number), sortRank: types.maybe(types.number),
icon: types.maybe(types.string), icon: types.maybe(types.string),
@@ -22,4 +22,8 @@ export const PermissionsStoreItem = types
updateRole: role => { updateRole: role => {
self.role = role; self.role = role;
}, },
updatePermission(permission: number, permissionName: string) {
self.permission = permission;
self.permissionName = permissionName;
},
})); }));