From d35eca333feb144d841693552842848402973644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Sep 2018 13:56:09 +0200 Subject: [PATCH] folder permissions in redux --- .../PermissionList/AddPermission.tsx | 142 ++++++++++++++++++ .../PermissionList/PermissionList.tsx | 3 +- .../PermissionList/PermissionListItem.tsx | 4 +- .../features/folders/FolderPermissions.tsx | 36 +++-- .../folders/FolderSettingsPage.test.tsx | 1 + public/app/features/folders/state/actions.ts | 31 +++- public/app/features/folders/state/reducers.ts | 3 +- public/app/types/acl.ts | 27 ++++ public/app/types/index.ts | 5 - 9 files changed, 231 insertions(+), 21 deletions(-) create mode 100644 public/app/core/components/PermissionList/AddPermission.tsx diff --git a/public/app/core/components/PermissionList/AddPermission.tsx b/public/app/core/components/PermissionList/AddPermission.tsx new file mode 100644 index 00000000000..76bcfac4780 --- /dev/null +++ b/public/app/core/components/PermissionList/AddPermission.tsx @@ -0,0 +1,142 @@ +import React, { Component } from 'react'; +import { UserPicker, User } from 'app/core/components/Picker/UserPicker'; +import { TeamPicker, Team } from 'app/core/components/Picker/TeamPicker'; +import DescriptionPicker, { OptionWithDescription } from 'app/core/components/Picker/DescriptionPicker'; +import { + dashboardPermissionLevels, + dashboardAclTargets, + AclTarget, + PermissionLevel, + NewDashboardAclItem, +} from 'app/types/acl'; + +export interface Props { + onAddPermission: (item: NewDashboardAclItem) => void; + onCancel: () => void; +} + +class AddPermissions extends Component { + constructor(props) { + super(props); + this.state = this.getCleanState(); + } + + getCleanState() { + return { + userId: 0, + teamId: 0, + role: '', + type: AclTarget.Team, + permission: PermissionLevel.View, + }; + } + + onTypeChanged = evt => { + this.setState({ type: evt.target.value as AclTarget }); + }; + + onUserSelected = (user: User) => { + this.setState({ + userId: user ? user.id : 0, + teamId: 0, + }); + }; + + onTeamSelected = (team: Team) => { + this.setState({ + userId: 0, + teamId: team ? team.id : 0, + }); + }; + + onPermissionChanged = (permission: OptionWithDescription) => { + this.setState({ permission: permission.value }); + }; + + onSubmit = async evt => { + evt.preventDefault(); + await this.props.onAddPermission(this.state); + this.setState(this.getCleanState()); + }; + + isValid() { + switch (this.state.type) { + case AclTarget.Team: + return this.state.teamId > 0; + case AclTarget.User: + return this.state.userId > 0; + } + return true; + } + + render() { + const { onCancel } = this.props; + const newItem = this.state; + const pickerClassName = 'width-20'; + const isValid = this.isValid(); + + return ( +
+ +
+
Add Permission For
+
+
+
+ +
+
+ + {newItem.type === AclTarget.User ? ( +
+ +
+ ) : null} + + {newItem.type === AclTarget.Team ? ( +
+ +
+ ) : null} + +
+ +
+ +
+ +
+
+
+
+ ); + } +} + +export default AddPermissions; diff --git a/public/app/core/components/PermissionList/PermissionList.tsx b/public/app/core/components/PermissionList/PermissionList.tsx index 29f810a4358..772baa0c274 100644 --- a/public/app/core/components/PermissionList/PermissionList.tsx +++ b/public/app/core/components/PermissionList/PermissionList.tsx @@ -1,7 +1,8 @@ import React, { PureComponent } from 'react'; import PermissionsListItem from './PermissionListItem'; import DisabledPermissionsListItem from './DisabledPermissionListItem'; -import { DashboardAcl, FolderInfo } from 'app/types'; +import { FolderInfo } from 'app/types'; +import { DashboardAcl } from 'app/types/acl'; export interface Props { items: DashboardAcl[]; diff --git a/public/app/core/components/PermissionList/PermissionListItem.tsx b/public/app/core/components/PermissionList/PermissionListItem.tsx index 3e5aaf3ab2f..b846f98a063 100644 --- a/public/app/core/components/PermissionList/PermissionListItem.tsx +++ b/public/app/core/components/PermissionList/PermissionListItem.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker'; -import { dashboardPermissionLevels } from 'app/types/acl'; -import { DashboardAcl, FolderInfo, PermissionLevel } from 'app/types'; +import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl'; +import { FolderInfo } from 'app/types'; const setClassNameHelper = inherited => { return inherited ? 'gf-form-disabled' : ''; diff --git a/public/app/features/folders/FolderPermissions.tsx b/public/app/features/folders/FolderPermissions.tsx index 25de5f8be16..c86137a55ce 100644 --- a/public/app/features/folders/FolderPermissions.tsx +++ b/public/app/features/folders/FolderPermissions.tsx @@ -1,17 +1,23 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; import PageHeader from 'app/core/components/PageHeader/PageHeader'; -import Permissions from 'app/core/components/Permissions/Permissions'; import Tooltip from 'app/core/components/Tooltip/Tooltip'; -import PermissionsInfo from 'app/core/components/Permissions/PermissionsInfo'; -import AddPermissions from 'app/core/components/Permissions/AddPermissions'; import SlideDown from 'app/core/components/Animations/SlideDown'; import { getNavModel } from 'app/core/selectors/navModel'; -import { NavModel, StoreState, FolderState, DashboardAcl, PermissionLevel } from 'app/types'; -import { getFolderByUid, getFolderPermissions, updateFolderPermission, removeFolderPermission } from './state/actions'; +import { NavModel, StoreState, FolderState } from 'app/types'; +import { DashboardAcl, PermissionLevel, NewDashboardAclItem } from 'app/types/acl'; +import { + getFolderByUid, + getFolderPermissions, + updateFolderPermission, + removeFolderPermission, + addFolderPermission, +} from './state/actions'; import { getLoadingNav } from './state/navModel'; import PermissionList from 'app/core/components/PermissionList/PermissionList'; +import AddPermission from 'app/core/components/PermissionList/AddPermission'; +import PermissionsInfo from 'app/core/components/Permissions/PermissionsInfo'; export interface Props { navModel: NavModel; @@ -21,13 +27,14 @@ export interface Props { getFolderPermissions: typeof getFolderPermissions; updateFolderPermission: typeof updateFolderPermission; removeFolderPermission: typeof removeFolderPermission; + addFolderPermission: typeof addFolderPermission; } export interface State { isAdding: boolean; } -export class FolderPermissions extends Component { +export class FolderPermissions extends PureComponent { constructor(props) { super(props); @@ -53,6 +60,14 @@ export class FolderPermissions extends Component { this.props.updateFolderPermission(item, level); }; + onAddPermission = (newItem: NewDashboardAclItem) => { + return this.props.addFolderPermission(newItem); + }; + + onCancelAddPermission = () => { + this.setState({ isAdding: false }); + }; + render() { const { navModel, folder } = this.props; const { isAdding } = this.state; @@ -61,8 +76,7 @@ export class FolderPermissions extends Component { return ; } - const dashboardId = folder.id; - const folderInfo = { title: folder.tile, url: folder.url, id: folder.id }; + const folderInfo = { title: folder.title, url: folder.url, id: folder.id }; return (
@@ -78,6 +92,9 @@ export class FolderPermissions extends Component { Add Permission
+ + + { url: 'url', hasChanged: false, version: 1, + permissions: [], }, getFolderByUid: jest.fn(), setFolderTitle: jest.fn(), diff --git a/public/app/features/folders/state/actions.ts b/public/app/features/folders/state/actions.ts index 29940cc7a31..4f15f813a68 100644 --- a/public/app/features/folders/state/actions.ts +++ b/public/app/features/folders/state/actions.ts @@ -1,14 +1,15 @@ import { getBackendSrv } from 'app/core/services/backend_srv'; import { StoreState } from 'app/types'; import { ThunkAction } from 'redux-thunk'; +import { FolderDTO, FolderState } from 'app/types'; import { - FolderDTO, - FolderState, DashboardAcl, DashboardAclDTO, PermissionLevel, DashboardAclUpdateDTO, -} from 'app/types'; + NewDashboardAclItem, +} from 'app/types/acl'; + import { updateNavIndex, updateLocation } from 'app/core/actions'; import { buildNavModel } from './navModel'; import appEvents from 'app/core/app_events'; @@ -140,3 +141,27 @@ export function removeFolderPermission(itemToDelete: DashboardAcl): ThunkResult< await dispatch(getFolderPermissions(folder.uid)); }; } + +export function addFolderPermission(newItem: NewDashboardAclItem): ThunkResult { + return async (dispatch, getStore) => { + const folder = getStore().folder; + const itemsToUpdate = []; + + for (const item of folder.permissions) { + if (item.inherited) { + continue; + } + itemsToUpdate.push(toUpdateItem(item)); + } + + itemsToUpdate.push({ + userId: newItem.userId, + teamId: newItem.teamId, + role: item.role, + permission: item.permission, + }); + + await getBackendSrv().post(`/api/folders/${folder.uid}/permissions`, { items: itemsToUpdate }); + await dispatch(getFolderPermissions(folder.uid)); + }; +} diff --git a/public/app/features/folders/state/reducers.ts b/public/app/features/folders/state/reducers.ts index 6e6a671685a..9b73312790c 100644 --- a/public/app/features/folders/state/reducers.ts +++ b/public/app/features/folders/state/reducers.ts @@ -1,4 +1,5 @@ -import { FolderState, DashboardAcl, DashboardAclDTO } from 'app/types'; +import { FolderState } from 'app/types'; +import { DashboardAcl, DashboardAclDTO } from 'app/types/acl'; import { Action, ActionTypes } from './actions'; export const inititalState: FolderState = { diff --git a/public/app/types/acl.ts b/public/app/types/acl.ts index d77fc4793fc..feca062b355 100644 --- a/public/app/types/acl.ts +++ b/public/app/types/acl.ts @@ -43,12 +43,39 @@ export interface DashboardPermissionInfo { description: string; } +export interface NewDashboardAclItem { + teamId: number; + userId: number; + role: string; + permission: PermissionLevel; + type: AclTarget; +} + export enum PermissionLevel { View = 1, Edit = 2, Admin = 4, } +export enum AclTarget { + Team = 'team', + User = 'user', + Viewer = 'viewer', + Editor = 'editor', +} + +export interface AclTargetInfo { + value: AclTarget; + text: string; +} + +export const dashboardAclTargets: AclTargetInfo[] = [ + { value: AclTarget.Team, text: 'Team' }, + { value: AclTarget.User, text: 'User' }, + { value: AclTarget.Viewer, text: 'Everyone With Viewer Role' }, + { value: AclTarget.Editor, text: 'Everyone With Editor Role' }, +]; + export const dashboardPermissionLevels: DashboardPermissionInfo[] = [ { value: PermissionLevel.View, label: 'View', description: 'Can view dashboards.' }, { value: PermissionLevel.Edit, label: 'Edit', description: 'Can add, edit and delete dashboards.' }, diff --git a/public/app/types/index.ts b/public/app/types/index.ts index 49f7fdb0f28..6f052c7c503 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -3,7 +3,6 @@ import { AlertRuleDTO, AlertRule, AlertRulesState } from './alerting'; import { LocationState, LocationUpdate, UrlQueryMap, UrlQueryValue } from './location'; import { NavModel, NavModelItem, NavIndex } from './navModel'; import { FolderDTO, FolderState, FolderInfo } from './folder'; -import { DashboardAcl, DashboardAclDTO, PermissionLevel, DashboardAclUpdateDTO } from './acl'; export { Team, @@ -24,10 +23,6 @@ export { FolderDTO, FolderState, FolderInfo, - DashboardAcl, - DashboardAclDTO, - DashboardAclUpdateDTO, - PermissionLevel, }; export interface StoreState {