@xh/hoist 60.0.1 → 60.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 60.1.0 - 2024-01-18
4
+
5
+ ### 🐞 Bug Fixes
6
+ * Fixed transparent background for popup inline editors.
7
+ * Exceptions that occur in custom application tooltips will now be caught and logged to console,
8
+ rather than crashing grid.
9
+
10
+ ### ⚙️ Technical
11
+ * Improvements to exception handling during app initialization
12
+
3
13
  ## 60.0.1 - 2024-01-16
4
14
 
5
15
  ### 🐞 Bug Fixes
@@ -291,12 +291,12 @@ export class RoleModel extends HoistModel {
291
291
  fieldSpecs: compact([
292
292
  'name',
293
293
  'category',
294
- config.assignUsers && 'users',
295
- config.assignDirectoryGroups && 'directoryGroups',
294
+ config.userAssignmentSupported && 'users',
295
+ config.directoryGroupsSupported && 'directoryGroups',
296
296
  'roles',
297
297
  'inheritedRoleNames',
298
298
  'effectiveUserNames',
299
- config.assignDirectoryGroups && 'effectiveDirectoryGroupNames',
299
+ config.directoryGroupsSupported && 'effectiveDirectoryGroupNames',
300
300
  'effectiveRoleNames',
301
301
  'lastUpdatedBy',
302
302
  {
@@ -38,12 +38,7 @@ export type RoleMemberType = 'USER' | 'DIRECTORY_GROUP' | 'ROLE';
38
38
 
39
39
  export interface RoleModuleConfig {
40
40
  enabled: boolean;
41
- assignDirectoryGroups: boolean;
42
- assignUsers: boolean;
43
- refreshIntervalSecs: number;
44
- infoTooltips: {
45
- users: string;
46
- directoryGroups: string;
47
- roles: string;
48
- };
41
+ userAssignmentSupported: boolean;
42
+ directoryGroupsSupported: boolean;
43
+ directoryGroupsDescription: string;
49
44
  }
@@ -104,11 +104,11 @@ const count = hoistCmp.factory<CountProps>(({count, icon}) =>
104
104
 
105
105
  const bbar = hoistCmp.factory<RoleMembersModel>(({model}) => {
106
106
  const {directCounts, moduleConfig} = model;
107
- if (!moduleConfig?.assignDirectoryGroups && directCounts.DIRECTORY_GROUP) {
107
+ if (!moduleConfig?.directoryGroupsSupported && directCounts.DIRECTORY_GROUP) {
108
108
  return warningBanner({
109
109
  message: 'Directory Groups disabled. Will ignore.'
110
110
  });
111
- } else if (!moduleConfig?.assignUsers && directCounts.USER) {
111
+ } else if (!moduleConfig?.userAssignmentSupported && directCounts.USER) {
112
112
  return warningBanner({
113
113
  message: 'Users assignment disabled. Will ignore.'
114
114
  });
@@ -57,7 +57,7 @@ export class RoleMembersModel extends HoistModel {
57
57
  const {effectiveUsers, effectiveDirectoryGroups, effectiveRoles} = this.selectedRole;
58
58
  return {
59
59
  USER: effectiveUsers.length,
60
- DIRECTORY_GROUP: this.moduleConfig?.assignDirectoryGroups
60
+ DIRECTORY_GROUP: this.moduleConfig?.directoryGroupsSupported
61
61
  ? effectiveDirectoryGroups.length
62
62
  : 0,
63
63
  ROLE: effectiveRoles.length
@@ -105,7 +105,7 @@ export class RoleMembersModel extends HoistModel {
105
105
  })),
106
106
 
107
107
  // 2 - Directory Groups
108
- ...(this.moduleConfig?.assignDirectoryGroups
108
+ ...(this.moduleConfig?.directoryGroupsSupported
109
109
  ? role.effectiveDirectoryGroups.map(it => ({
110
110
  name: it.name,
111
111
  sources: this.sortThisRoleFirst(it.sourceRoles.map(role => ({role}))),
@@ -234,8 +234,8 @@ export class RoleMembersModel extends HoistModel {
234
234
 
235
235
  if (
236
236
  !includeEffective &&
237
- ((type === types.USER && !moduleConfig.assignUsers) ||
238
- (isDirectoryGroup && !moduleConfig.assignDirectoryGroups))
237
+ ((type === types.USER && !moduleConfig.userAssignmentSupported) ||
238
+ (isDirectoryGroup && !moduleConfig.directoryGroupsSupported))
239
239
  ) {
240
240
  return {hidden: true};
241
241
  }
@@ -68,12 +68,12 @@ const assignments = hoistCmp.factory<RoleFormModel>(({model}) =>
68
68
  items: [
69
69
  assignmentsPanel({
70
70
  entity: 'USER',
71
- omit: !model.moduleConfig?.assignUsers && model.usersGridModel.empty
71
+ omit: !model.moduleConfig?.userAssignmentSupported && model.usersGridModel.empty
72
72
  }),
73
73
  assignmentsPanel({
74
74
  entity: 'DIRECTORY_GROUP',
75
75
  omit:
76
- !model.moduleConfig?.assignDirectoryGroups &&
76
+ !model.moduleConfig?.directoryGroupsSupported &&
77
77
  model.directoryGroupsGridModel.empty
78
78
  }),
79
79
  assignmentsPanel({entity: 'ROLE'})
@@ -138,13 +138,13 @@ const bbar = hoistCmp.factory<AssignmentsPanelProps>(({entity, model}) => {
138
138
  return warningBanner({
139
139
  compact: true,
140
140
  message: 'Users assignment disabled. Will ignore.',
141
- omit: model.moduleConfig?.assignUsers
141
+ omit: model.moduleConfig?.userAssignmentSupported
142
142
  });
143
143
  case 'DIRECTORY_GROUP':
144
144
  return warningBanner({
145
145
  compact: true,
146
146
  message: 'Directory Groups disabled. Will ignore.',
147
- omit: model.moduleConfig?.assignDirectoryGroups
147
+ omit: model.moduleConfig?.directoryGroupsSupported
148
148
  });
149
149
  default:
150
150
  return null;
@@ -153,15 +153,20 @@ const bbar = hoistCmp.factory<AssignmentsPanelProps>(({entity, model}) => {
153
153
 
154
154
  const infoIcon = hoistCmp.factory<AssignmentsPanelProps>({
155
155
  render({entity, model}) {
156
- const tooltips = model.moduleConfig?.infoTooltips,
157
- tooltipText =
158
- entity === 'USER'
159
- ? tooltips?.users ?? 'All users listed here will be directly granted this role.'
160
- : entity === 'DIRECTORY_GROUP'
161
- ? tooltips?.directoryGroups ??
162
- 'All members of these directory groups will be granted this role.'
163
- : tooltips?.roles ??
164
- 'All users holding these roles will also be granted this role.';
156
+ let tooltipText = null;
157
+ switch (entity) {
158
+ case 'USER':
159
+ tooltipText = 'All users listed here will be directly granted this role.';
160
+ break;
161
+ case 'DIRECTORY_GROUP':
162
+ tooltipText =
163
+ model.moduleConfig?.directoryGroupsDescription ??
164
+ 'All members of these directory groups will be granted this role.';
165
+ break;
166
+ case 'ROLE':
167
+ tooltipText = 'All users holding these roles will also be granted this role.';
168
+ break;
169
+ }
165
170
  return tooltip({
166
171
  item: Icon.info(),
167
172
  content: tooltipText,
@@ -445,6 +445,9 @@ body.xh-app {
445
445
  .ag-popup-child {
446
446
  z-index: 30;
447
447
  }
448
+ .ag-popup-editor {
449
+ background: var(--xh-grid-bg);
450
+ }
448
451
  }
449
452
 
450
453
  //------------------------
@@ -42,7 +42,7 @@ import {
42
42
  } from 'react';
43
43
  import {GridModel} from '../GridModel';
44
44
  import {GridSorter} from '../GridSorter';
45
- import {managedRenderer} from '../impl/Utils';
45
+ import {getAgHeaderClassFn, managedRenderer} from '../impl/Utils';
46
46
  import {
47
47
  ColumnCellClassFn,
48
48
  ColumnCellClassRuleFn,
@@ -62,10 +62,8 @@ import {
62
62
  } from '../Types';
63
63
  import {ExcelFormat} from '../enums/ExcelFormat';
64
64
  import {FunctionComponent} from 'react';
65
- import {ColumnGroup} from './ColumnGroup';
66
65
  import type {
67
66
  ColDef,
68
- HeaderClassParams,
69
67
  ITooltipParams,
70
68
  ValueGetterParams,
71
69
  ValueSetterParams
@@ -817,9 +815,15 @@ export class Column {
817
815
  store
818
816
  });
819
817
 
820
- ret = isFunction(tooltip)
821
- ? tooltip(val, {record, column: this, gridModel, agParams})
822
- : val;
818
+ if (isFunction(tooltip)) {
819
+ try {
820
+ ret = tooltip(val, {record, gridModel, agParams, column: this});
821
+ } catch (e) {
822
+ logWarn([`Failure in tooltip for '${this.displayName}'`, e], 'Column');
823
+ }
824
+ } else {
825
+ ret = val;
826
+ }
823
827
  }
824
828
 
825
829
  const isElement = isValidElement(ret);
@@ -1083,34 +1087,3 @@ export class Column {
1083
1087
  : record?.data[sortValue] ?? v;
1084
1088
  }
1085
1089
  }
1086
-
1087
- export function getAgHeaderClassFn(
1088
- column: Column | ColumnGroup
1089
- ): (params: HeaderClassParams) => string[] {
1090
- // Generate CSS classes for headers.
1091
- // Default alignment classes are mixed in with any provided custom classes.
1092
- const {headerClass, headerAlign, gridModel} = column;
1093
-
1094
- return agParams => {
1095
- let r = [];
1096
- if (headerClass) {
1097
- r = castArray(
1098
- isFunction(headerClass) ? headerClass({column, gridModel, agParams}) : headerClass
1099
- );
1100
- }
1101
-
1102
- if (headerAlign === 'center' || headerAlign === 'right') {
1103
- r.push('xh-column-header-align-' + headerAlign);
1104
- }
1105
-
1106
- if (column instanceof Column && column.isTreeColumn && column.headerHasExpandCollapse) {
1107
- r.push('xh-column-header--with-expand-collapse');
1108
- }
1109
-
1110
- if (gridModel.headerMenuDisplay === 'hover') {
1111
- r.push('xh-column-header--hoverable');
1112
- }
1113
-
1114
- return r;
1115
- };
1116
- }
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * Copyright © 2023 Extremely Heavy Industries Inc.
6
6
  */
7
+ import {getAgHeaderClassFn} from '@xh/hoist/cmp/grid/impl/Utils';
7
8
  import {HAlign, PlainObject, Some, Thunkable, XH} from '@xh/hoist/core';
8
9
  import {genDisplayName} from '@xh/hoist/data';
9
10
 
@@ -13,7 +14,7 @@ import {clone, isEmpty, isFunction, isString, keysIn} from 'lodash';
13
14
  import {ReactNode} from 'react';
14
15
  import {GridModel} from '../GridModel';
15
16
  import {ColumnHeaderClassFn, ColumnHeaderNameFn} from '../Types';
16
- import {Column, ColumnSpec, getAgHeaderClassFn} from './Column';
17
+ import {Column, ColumnSpec} from './Column';
17
18
 
18
19
  export interface ColumnGroupSpec {
19
20
  /** Column or ColumnGroup configs for children of this group.*/
@@ -4,12 +4,11 @@
4
4
  *
5
5
  * Copyright © 2023 Extremely Heavy Industries Inc.
6
6
  */
7
- import {ColumnRenderer, GroupRowRenderer} from '@xh/hoist/cmp/grid';
8
- import {isFunction} from 'lodash';
7
+ import {Column, ColumnGroup, ColumnRenderer, GroupRowRenderer} from '@xh/hoist/cmp/grid';
8
+ import {HeaderClassParams} from '@xh/hoist/kit/ag-grid';
9
+ import {castArray, isFunction} from 'lodash';
9
10
 
10
- /**
11
- * @internal
12
- */
11
+ /** @internal */
13
12
  export function managedRenderer<T extends ColumnRenderer | GroupRowRenderer>(
14
13
  fn: T,
15
14
  identifier: string
@@ -24,3 +23,38 @@ export function managedRenderer<T extends ColumnRenderer | GroupRowRenderer>(
24
23
  }
25
24
  } as unknown as T;
26
25
  }
26
+
27
+ /**
28
+ * Generate CSS classes for headers.
29
+ * Default alignment classes are mixed in with any provided custom classes.
30
+ *
31
+ * @internal
32
+ */
33
+ export function getAgHeaderClassFn(
34
+ column: Column | ColumnGroup
35
+ ): (params: HeaderClassParams) => string[] {
36
+ const {headerClass, headerAlign, gridModel} = column;
37
+
38
+ return agParams => {
39
+ let r = [];
40
+ if (headerClass) {
41
+ r = castArray(
42
+ isFunction(headerClass) ? headerClass({column, gridModel, agParams}) : headerClass
43
+ );
44
+ }
45
+
46
+ if (headerAlign === 'center' || headerAlign === 'right') {
47
+ r.push('xh-column-header-align-' + headerAlign);
48
+ }
49
+
50
+ if (column instanceof Column && column.isTreeColumn && column.headerHasExpandCollapse) {
51
+ r.push('xh-column-header--with-expand-collapse');
52
+ }
53
+
54
+ if (gridModel.headerMenuDisplay === 'hover') {
55
+ r.push('xh-column-header--hoverable');
56
+ }
57
+
58
+ return r;
59
+ };
60
+ }
@@ -168,12 +168,13 @@ export class Exception {
168
168
  origin = match
169
169
  ? match[0]
170
170
  : protocolPattern.test(XH.baseUrl)
171
- ? XH.baseUrl
172
- : window.location.origin;
171
+ ? XH.baseUrl
172
+ : window.location.origin;
173
173
 
174
174
  return this.createFetchException({
175
175
  name: 'Server Unavailable',
176
176
  message: `Unable to contact the server at ${origin}`,
177
+ isServerUnavailable: true,
177
178
  fetchOptions,
178
179
  cause
179
180
  });
@@ -163,7 +163,7 @@ export const [FormField, formField] = hoistCmp.withFactory<FormFieldProps>({
163
163
  if (disabled) classes.push('xh-form-field-disabled');
164
164
  if (displayNotValid) classes.push('xh-form-field-invalid');
165
165
 
166
- const testId = getFormFieldTestId(props, formContext, model.name);
166
+ const testId = getFormFieldTestId(props, formContext, model?.name);
167
167
  useOnMount(() => instanceManager.registerModelWithTestId(testId, model));
168
168
  useOnUnmount(() => instanceManager.unregisterModelWithTestId(testId));
169
169
 
@@ -369,5 +369,8 @@ function getFormFieldTestId(
369
369
  formContext: FormContextType,
370
370
  fieldName: string
371
371
  ): string {
372
- return props.testId ?? (formContext.testId ? `${formContext.testId}-${fieldName}` : undefined);
372
+ return (
373
+ props.testId ??
374
+ (formContext.testId && fieldName ? `${formContext.testId}-${fieldName}` : undefined)
375
+ );
373
376
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "60.0.1",
3
+ "version": "60.1.0",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",
@@ -46,19 +46,22 @@ export class FetchService extends HoistService {
46
46
  try {
47
47
  await this.fetch({url: 'ping'});
48
48
  } catch (e) {
49
- const {baseUrl} = XH,
50
- pingURL = baseUrl.startsWith('http')
51
- ? `${baseUrl}ping`
52
- : `${window.location.origin}${baseUrl}ping`;
53
-
54
- throw XH.exception({
55
- name: 'UI Server Unavailable',
56
- detail: e.message,
57
- message:
58
- 'Client cannot reach UI server. Please check UI server at the ' +
59
- `following location: ${pingURL}`,
60
- logOnServer: false
61
- });
49
+ if (e.isServerUnavailable) {
50
+ const {baseUrl} = XH,
51
+ pingURL = baseUrl.startsWith('http')
52
+ ? `${baseUrl}ping`
53
+ : `${window.location.origin}${baseUrl}ping`;
54
+
55
+ throw XH.exception({
56
+ name: 'UI Server Unavailable',
57
+ detail: e.message,
58
+ message:
59
+ 'Client cannot reach UI server. Please check UI server at the ' +
60
+ `following location: ${pingURL}`
61
+ });
62
+ }
63
+
64
+ throw e;
62
65
  }
63
66
  }
64
67