Skip to content

Commit

Permalink
Role Improvements (#3557)
Browse files Browse the repository at this point in the history
cr: Lee and Greg
  • Loading branch information
ghsolomon authored Jan 10, 2024
1 parent baf8916 commit 0b16581
Show file tree
Hide file tree
Showing 12 changed files with 354 additions and 135 deletions.
64 changes: 51 additions & 13 deletions admin/tabs/general/roles/RoleModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,39 @@ import {Icon} from '@xh/hoist/icon';
import {bindable, makeObservable} from '@xh/hoist/mobx';
import {wait} from '@xh/hoist/promise';
import {compact, groupBy, mapValues} from 'lodash';
import {action, observable} from 'mobx';
import {action, observable, runInAction} from 'mobx';
import moment from 'moment/moment';
import {RoleEditorModel} from './editor/RoleEditorModel';
import {HoistRole, RoleMemberType, RoleServiceConfig} from './Types';
import {HoistRole, RoleMemberType, RoleModuleConfig} from './Types';

export class RoleModel extends HoistModel {
static PERSIST_WITH = {localStorageKey: 'xhAdminRolesState'};

static fmtDirectoryGroup(name: string): string {
if (!name) return name;
const parts = name.split(','),
cn = parts.find(it => it.toLowerCase().startsWith('cn='));
return cn ?? name;
}

override persistWith = RoleModel.PERSIST_WITH;

@managed readonly gridModel: GridModel = this.createGridModel();
@managed readonly filterChooserModel: FilterChooserModel = this.createFilterChooserModel();
@managed gridModel: GridModel;
@managed filterChooserModel: FilterChooserModel;
@managed readonly roleEditorModel = new RoleEditorModel(this);

@observable.ref allRoles: HoistRole[] = [];

@bindable @persist groupByCategory = true;

@observable private shouldShowFilterChooser = false;

@observable.ref moduleConfig: RoleModuleConfig;

get isFilterChooserVisible(): boolean {
return this.shouldShowFilterChooser || !!this.filterChooserModel.value;
}

get readonly() {
return !XH.getUser().isHoistRoleManager;
}
Expand All @@ -34,19 +49,18 @@ export class RoleModel extends HoistModel {
return this.gridModel.selectedRecord?.data as HoistRole;
}

get softConfig(): RoleServiceConfig {
return XH.getConf('xhRoleModuleConfig');
}

constructor() {
super();
makeObservable(this);
this.addReaction(this.groupByCategoryReaction());
}

override async doLoadAsync(loadSpec: LoadSpec) {
if (!this.softConfig?.enabled) return;
try {
await this.ensureInitializedAsync();

if (!this.moduleConfig.enabled) return;

const {data} = await XH.fetchJson({loadSpec, url: 'roleAdmin/list'});
if (loadSpec.isStale) return;
this.setRoles(this.processRolesFromServer(data));
Expand Down Expand Up @@ -78,6 +92,16 @@ export class RoleModel extends HoistModel {
this.gridModel.clear();
}

@action
toggleFilterChooserVisibility() {
if (this.isFilterChooserVisible) {
this.filterChooserModel.setValue(null);
this.shouldShowFilterChooser = false;
} else {
this.shouldShowFilterChooser = true;
}
}

applyMemberFilter(name: string, type: RoleMemberType, includeEffective: boolean) {
const {gridModel} = this,
field = this.getFieldForMemberType(type, includeEffective);
Expand Down Expand Up @@ -147,6 +171,19 @@ export class RoleModel extends HoistModel {
// -------------------------------
// Implementation
// -------------------------------
private async ensureInitializedAsync() {
if (!this.moduleConfig) {
const config = await XH.fetchJson({url: 'roleAdmin/config'});
runInAction(() => {
this.moduleConfig = config;
if (config.enabled) {
this.gridModel = this.createGridModel();
this.filterChooserModel = this.createFilterChooserModel();
}
});
}
}

private groupByCategoryReaction(): ReactionSpec<boolean> {
const {gridModel} = this;
return {
Expand Down Expand Up @@ -217,6 +254,7 @@ export class RoleModel extends HoistModel {
{name: 'effectiveUsers', type: 'json'},
{name: 'effectiveDirectoryGroups', type: 'json'},
{name: 'effectiveRoles', type: 'json'},
{name: 'errors', type: 'json'},
{name: 'inheritedRoleNames', displayName: 'Inherited Roles', type: 'tags'},
{name: 'effectiveUserNames', displayName: 'Users', type: 'tags'},
{
Expand Down Expand Up @@ -259,18 +297,18 @@ export class RoleModel extends HoistModel {
}

private createFilterChooserModel(): FilterChooserModel {
const {softConfig} = this;
const config = this.moduleConfig;
return new FilterChooserModel({
bind: this.gridModel.store,
fieldSpecs: compact([
'name',
'category',
softConfig.assignUsers && 'users',
softConfig.assignDirectoryGroups && 'directoryGroups',
config.assignUsers && 'users',
config.assignDirectoryGroups && 'directoryGroups',
'roles',
'inheritedRoleNames',
'effectiveUserNames',
'effectiveDirectoryGroupNames',
config.assignDirectoryGroups && 'effectiveDirectoryGroupNames',
'effectiveRoleNames',
'lastUpdatedBy',
{
Expand Down
32 changes: 25 additions & 7 deletions admin/tabs/general/roles/RolePanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {roleGraph} from '@xh/hoist/admin/tabs/general/roles/graph/RoleGraph';
import {grid, gridCountLabel} from '@xh/hoist/cmp/grid';
import {filler, fragment, hframe, vframe} from '@xh/hoist/cmp/layout';
import {creates, hoistCmp} from '@xh/hoist/core';
import {button} from '@xh/hoist/desktop/cmp/button';
import {errorMessage} from '@xh/hoist/desktop/cmp/error';
import {filterChooser} from '@xh/hoist/desktop/cmp/filter';
import {switchInput} from '@xh/hoist/desktop/cmp/input';
Expand All @@ -17,13 +18,13 @@ export const rolePanel = hoistCmp.factory({
displayName: 'Roles',
model: creates(RoleModel),
render({className, model}) {
if (!model.softConfig?.enabled) {
return errorMessage({
error: 'Role Service disabled via xhRoleModuleConfig.'
});
const {moduleConfig} = model;
if (!moduleConfig) return null;
if (!moduleConfig.enabled) {
return errorMessage({error: 'Default Role Module not enabled.'});
}

const {gridModel, readonly} = model;
const {gridModel, isFilterChooserVisible, readonly} = model;
return fragment(
panel({
className,
Expand All @@ -38,15 +39,32 @@ export const rolePanel = hoistCmp.factory({
filler(),
gridCountLabel({unit: 'role'}),
'-',
filterChooser({flex: 2}),
button({
icon: isFilterChooserVisible
? Icon.filterSlash({prefix: 'fas'})
: Icon.filter(),
onClick: () => model.toggleFilterChooserVisibility()
}),
'-',
switchInput({
bind: 'groupByCategory',
label: 'Group By Category',
labelSide: 'left'
})
],
item: hframe(vframe(grid(), roleGraph()), detailsPanel())
items: [
toolbar({
omit: !isFilterChooserVisible,
items: [
filterChooser({enableClear: false, flex: 1}),
button({
icon: Icon.close(),
onClick: () => model.toggleFilterChooserVisibility()
})
]
}),
hframe(vframe(grid(), roleGraph()), detailsPanel())
]
}),
roleEditor()
);
Expand Down
10 changes: 9 additions & 1 deletion admin/tabs/general/roles/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export interface HoistRole {
lastUpdated: Date;
lastUpdatedBy: string;
members: HoistRoleMember[];
errors: {
directoryGroups: Record<string, string>;
};
}

export interface HoistRoleMember {
Expand All @@ -33,9 +36,14 @@ export interface EffectiveRoleUser {

export type RoleMemberType = 'USER' | 'DIRECTORY_GROUP' | 'ROLE';

export interface RoleServiceConfig {
export interface RoleModuleConfig {
enabled: boolean;
assignDirectoryGroups: boolean;
assignUsers: boolean;
refreshIntervalSecs: number;
infoTooltips: {
users: string;
directoryGroups: string;
roles: string;
};
}
15 changes: 9 additions & 6 deletions admin/tabs/general/roles/details/RoleDetailsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ export class RoleDetailsModel extends HoistModel {
this.addReaction({
track: () => this.role,
run: role => {
if (!role) this.formModel.init({});
this.formModel.init({
...role,
category: role.category ?? 'Uncategorized',
lastUpdated: `${role.lastUpdatedBy} (${fmtDateTimeSec(role.lastUpdated)})`
});
if (!role) {
this.formModel.init({});
} else {
this.formModel.init({
...role,
category: role.category ?? 'Uncategorized',
lastUpdated: `${role.lastUpdatedBy} (${fmtDateTimeSec(role.lastUpdated)})`
});
}
}
});
}
Expand Down
42 changes: 30 additions & 12 deletions admin/tabs/general/roles/details/members/RoleMembers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {buttonGroupInput} from '@xh/hoist/desktop/cmp/input';
import {panel} from '@xh/hoist/desktop/cmp/panel';
import './RoleMembers.scss';
import {Icon} from '@xh/hoist/icon';
import {sum, values} from 'lodash';
import {tooltip} from '@xh/hoist/kit/blueprint';
import {pluralize} from '@xh/hoist/utils/js';
import {isEmpty, sum, values} from 'lodash';
import {ReactNode} from 'react';

export interface RoleMembersProps extends HoistProps<RoleMembersModel> {
Expand All @@ -31,17 +33,11 @@ export const roleMembers = hoistCmp.factory<RoleMembersProps>({
bind: 'activeTabId',
items: [
button({
text: hbox({
alignItems: 'center',
items: ['Assigned', counts({countsByType: directCounts})]
}),
text: buttonText({text: 'Assigned', countsByType: directCounts}),
value: 'directMembers'
}),
button({
text: hbox({
alignItems: 'center',
items: ['Effective', counts({countsByType: effectiveCounts})]
}),
text: buttonText({text: 'Effective', countsByType: effectiveCounts}),
value: 'effectiveMembers'
})
]
Expand All @@ -55,6 +51,28 @@ export const roleMembers = hoistCmp.factory<RoleMembersProps>({
}
});

interface ButtonTextProps extends HoistProps {
countsByType: Record<RoleMemberType, number>;
text: string;
}

const buttonText = hoistCmp.factory<ButtonTextProps>(({countsByType, text}) => {
const countLabels = [],
{USER: users, DIRECTORY_GROUP: groups, ROLE: roles} = countsByType;

if (users) countLabels.push(pluralize('User', users, true));
if (groups) countLabels.push(pluralize('Directory Group', groups, true));
if (roles) countLabels.push(pluralize('Role', roles, true));

return tooltip({
content: `${text} Members` + (isEmpty(countLabels) ? '' : ` (${countLabels.join(', ')})`),
item: hbox({
alignItems: 'center',
items: [text, counts({countsByType})]
})
});
});

interface CountsProps extends HoistProps {
countsByType: Record<RoleMemberType, number>;
}
Expand Down Expand Up @@ -85,12 +103,12 @@ const count = hoistCmp.factory<CountProps>(({count, icon}) =>
);

const bbar = hoistCmp.factory<RoleMembersModel>(({model}) => {
const {directCounts, softConfig} = model;
if (!softConfig?.assignDirectoryGroups && directCounts.DIRECTORY_GROUP) {
const {directCounts, moduleConfig} = model;
if (!moduleConfig?.assignDirectoryGroups && directCounts.DIRECTORY_GROUP) {
return warningBanner({
message: 'Directory Groups disabled. Will ignore.'
});
} else if (!softConfig?.assignUsers && directCounts.USER) {
} else if (!moduleConfig?.assignUsers && directCounts.USER) {
return warningBanner({
message: 'Users assignment disabled. Will ignore.'
});
Expand Down
Loading

0 comments on commit 0b16581

Please sign in to comment.