@xh/hoist 68.0.0-SNAPSHOT.1726665376598 → 68.0.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,10 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## 68.0.0-SNAPSHOT - unreleased
3
+ ## 68.0.0 - 2024-09-18
4
4
 
5
5
  ### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW - Hoist Core update only)
6
6
 
7
- * Requires `hoist-core >= 21.1` for consolidated polling of Alert Banner updates (see below).
7
+ * Requires `hoist-core >= 22.0` for consolidated polling of Alert Banner updates (see below).
8
8
 
9
9
  ### 🎁 New Features
10
10
 
@@ -27,22 +27,24 @@ export const AppComponent = hoistCmp({
27
27
  }
28
28
  });
29
29
 
30
- const tbar = hoistCmp.factory<AppModel>(({model}) =>
31
- appBar({
30
+ const tbar = hoistCmp.factory<AppModel>(({model}) => {
31
+ const primaryApp = model.getPrimaryAppCode();
32
+ return appBar({
32
33
  icon: Icon.gears({size: '2x', prefix: 'fal'}),
33
34
  leftItems: [tabSwitcher({testId: 'tab-switcher', enableOverflow: true})],
34
35
  rightItems: [
35
36
  button({
36
37
  icon: Icon.openExternal(),
37
- title: 'Open app...',
38
- onClick: () => window.open('/')
38
+ tooltip: `Open ${primaryApp}...`,
39
+ omit: !primaryApp,
40
+ onClick: () => model.openPrimaryApp()
39
41
  }),
40
- appBarSeparator()
42
+ appBarSeparator({omit: !primaryApp})
41
43
  ],
42
44
  appMenuButtonProps: {
43
45
  hideAdminItem: true,
44
46
  hideFeedbackItem: true,
45
47
  extraItems: model.getAppMenuButtonExtraItems()
46
48
  }
47
- })
48
- );
49
+ });
50
+ });
package/admin/AppModel.ts CHANGED
@@ -4,15 +4,16 @@
4
4
  *
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
+ import {clusterTab} from '@xh/hoist/admin/tabs/cluster/ClusterTab';
7
8
  import {GridModel} from '@xh/hoist/cmp/grid';
8
9
  import {TabConfig, TabContainerModel} from '@xh/hoist/cmp/tab';
9
10
  import {HoistAppModel, managed, XH} from '@xh/hoist/core';
10
11
  import {Icon} from '@xh/hoist/icon';
12
+ import {without} from 'lodash';
11
13
  import {Route} from 'router5';
12
14
  import {activityTab} from './tabs/activity/ActivityTab';
13
15
  import {generalTab} from './tabs/general/GeneralTab';
14
16
  import {monitorTab} from './tabs/monitor/MonitorTab';
15
- import {clusterTab} from '@xh/hoist/admin/tabs/cluster/ClusterTab';
16
17
  import {userDataTab} from './tabs/userData/UserDataTab';
17
18
 
18
19
  export class AppModel extends HoistAppModel {
@@ -136,4 +137,14 @@ export class AppModel extends HoistAppModel {
136
137
  }
137
138
  ];
138
139
  }
140
+
141
+ /** Open the primary business-facing application, typically 'app'. */
142
+ openPrimaryApp() {
143
+ window.open(`/${this.getPrimaryAppCode()}`);
144
+ }
145
+
146
+ getPrimaryAppCode() {
147
+ const appCodes = without(XH.clientApps, XH.clientAppCode, 'mobile');
148
+ return appCodes.find(it => it === 'app') ?? appCodes[0];
149
+ }
139
150
  }
@@ -5,10 +5,13 @@
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {RangeAggregator} from '@xh/hoist/admin/tabs/activity/aggregators/RangeAggregator';
8
+ import {badge} from '@xh/hoist/cmp/badge';
9
+ import {XH} from '@xh/hoist/core';
8
10
  import {Icon} from '@xh/hoist/icon';
9
11
  import {fmtDate, fmtSpan, numberRenderer} from '@xh/hoist/format';
10
12
  import * as Col from '@xh/hoist/cmp/grid/columns';
11
13
  import {ColumnSpec} from '@xh/hoist/cmp/grid/columns';
14
+ import copy from 'clipboard-copy';
12
15
 
13
16
  export const appEnvironment: ColumnSpec = {
14
17
  field: {
@@ -113,6 +116,7 @@ export const correlationId: ColumnSpec = {
113
116
  type: 'string',
114
117
  displayName: 'Correlation ID'
115
118
  },
119
+ renderer: badgeRenderer,
116
120
  width: 100
117
121
  };
118
122
 
@@ -121,8 +125,8 @@ export const error: ColumnSpec = {
121
125
  name: 'error',
122
126
  type: 'string'
123
127
  },
124
- flex: true,
125
- minWidth: 150,
128
+ width: 250,
129
+ autosizeMaxWidth: 400,
126
130
  renderer: e => fmtSpan(e, {className: 'xh-font-family-mono xh-font-size-small'})
127
131
  };
128
132
 
@@ -134,9 +138,8 @@ export const msg: ColumnSpec = {
134
138
  isDimension: true,
135
139
  aggregator: 'UNIQUE'
136
140
  },
137
- minWidth: 120,
138
- autosizeMaxWidth: 400,
139
- flex: true
141
+ width: 250,
142
+ autosizeMaxWidth: 400
140
143
  };
141
144
 
142
145
  export const url: ColumnSpec = {
@@ -156,6 +159,7 @@ export const instance: ColumnSpec = {
156
159
  isDimension: true,
157
160
  aggregator: 'UNIQUE'
158
161
  },
162
+ renderer: badgeRenderer,
159
163
  width: 100
160
164
  };
161
165
 
@@ -230,3 +234,20 @@ function dayRangeComparator(rangeA, rangeB, sortDir, abs, {defaultComparator}) {
230
234
 
231
235
  return defaultComparator(maxA, maxB);
232
236
  }
237
+
238
+ function badgeRenderer(v) {
239
+ return v
240
+ ? badge({
241
+ item: v,
242
+ className: 'xh-font-family-mono xh-title-tip',
243
+ title: 'Double-click to copy',
244
+ onDoubleClick: () => {
245
+ copy(v);
246
+ XH.toast({
247
+ icon: Icon.copy(),
248
+ message: `Copied ${v}`
249
+ });
250
+ }
251
+ })
252
+ : '-';
253
+ }
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
+ import {correlationId, instance} from '@xh/hoist/admin/columns';
7
8
  import {form} from '@xh/hoist/cmp/form';
8
9
  import {a, div, h3, hframe, span, vbox} from '@xh/hoist/cmp/layout';
9
10
  import {hoistCmp} from '@xh/hoist/core';
@@ -49,10 +50,6 @@ export const clientErrorDetail = hoistCmp.factory<ClientErrorsModel>(({model}) =
49
50
  return span(username, impSpan);
50
51
  }
51
52
  }),
52
- formField({
53
- field: 'dateCreated',
54
- readonlyRenderer: v => fmtDateTimeSec(v)
55
- }),
56
53
  formField({
57
54
  field: 'appVersion',
58
55
  readonlyRenderer: appVersion => {
@@ -65,12 +62,22 @@ export const clientErrorDetail = hoistCmp.factory<ClientErrorsModel>(({model}) =
65
62
  field: 'userAlerted',
66
63
  label: 'User Alerted?'
67
64
  }),
68
- formField({field: 'id'}),
69
- formField({field: 'correlationId'}),
70
65
  formField({
71
66
  field: 'url',
72
67
  readonlyRenderer: hyperlinkVal
73
68
  }),
69
+ formField({
70
+ field: 'instance',
71
+ readonlyRenderer: v => instance.renderer(v, null)
72
+ }),
73
+ formField({
74
+ field: 'correlationId',
75
+ readonlyRenderer: v => correlationId.renderer(v, null)
76
+ }),
77
+ formField({
78
+ field: 'dateCreated',
79
+ readonlyRenderer: v => fmtDateTimeSec(v)
80
+ }),
74
81
  h3(Icon.desktop(), 'Device / Browser'),
75
82
  formField({field: 'device'}),
76
83
  formField({field: 'browser'}),
@@ -115,4 +122,4 @@ export const clientErrorDetail = hoistCmp.factory<ClientErrorsModel>(({model}) =
115
122
 
116
123
  const valOrNa = v => (!isNil(v) ? v.toString() : naSpan());
117
124
  const naSpan = () => span({item: 'N/A', className: 'xh-text-color-muted'});
118
- const hyperlinkVal = v => a({href: v, item: v, target: '_blank'});
125
+ const hyperlinkVal = v => (v ? a({href: v, item: v, target: '_blank'}) : '-');
@@ -52,9 +52,10 @@ export class ClientErrorsModel extends HoistModel {
52
52
  columns: [
53
53
  {...Col.userMessageFlag},
54
54
  {...Col.userAlertedFlag},
55
- {...Col.entryId, hidden},
56
55
  {...Col.impersonatingFlag},
56
+ {...Col.entryId, hidden},
57
57
  {...Col.username},
58
+ {...Col.impersonating, hidden},
58
59
  {...Col.browser},
59
60
  {...Col.device},
60
61
  {...Col.userAgent, hidden},
@@ -63,11 +64,10 @@ export class ClientErrorsModel extends HoistModel {
63
64
  {...Col.msg, displayName: 'User Message', hidden},
64
65
  {...Col.error, hidden},
65
66
  {...Col.url},
67
+ {...Col.correlationId},
66
68
  {...Col.instance, hidden},
67
69
  {...Col.day},
68
- {...Col.dateCreatedWithSec, displayName: 'Timestamp'},
69
- {...Col.impersonating, hidden},
70
- {...Col.correlationId}
70
+ {...Col.dateCreatedWithSec, displayName: 'Timestamp'}
71
71
  ]
72
72
  });
73
73
 
@@ -49,17 +49,17 @@ export class ActivityDetailModel extends HoistModel {
49
49
  {...Col.impersonating, hidden},
50
50
  {...Col.category},
51
51
  {...Col.msg},
52
- {...Col.url},
53
- {...Col.instance, hidden},
52
+ {...Col.browser},
53
+ {...Col.device},
54
+ {...Col.userAgent, hidden},
54
55
  {...Col.appVersion},
55
56
  {...Col.appEnvironment, hidden},
56
57
  {...Col.data, hidden},
57
- {...Col.device},
58
- {...Col.browser},
59
- {...Col.userAgent, hidden},
58
+ {...Col.url},
59
+ {...Col.correlationId},
60
+ {...Col.instance, hidden},
60
61
  {...Col.elapsed},
61
- {...Col.dateCreatedWithSec, displayName: 'Timestamp'},
62
- {...Col.correlationId}
62
+ {...Col.dateCreatedWithSec, displayName: 'Timestamp'}
63
63
  ]
64
64
  });
65
65
 
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
+ import {correlationId, instance} from '@xh/hoist/admin/columns';
7
8
  import {form} from '@xh/hoist/cmp/form';
8
9
  import {grid, gridCountLabel} from '@xh/hoist/cmp/grid';
9
10
  import {a, div, filler, h3, hframe, placeholder, span} from '@xh/hoist/cmp/layout';
@@ -81,6 +82,7 @@ const detailRecForm = hoistCmp.factory<ActivityDetailModel>(({model}) => {
81
82
  }
82
83
  }),
83
84
  formField({field: 'category'}),
85
+ formField({field: 'msg'}),
84
86
  formField({
85
87
  field: 'appVersion',
86
88
  readonlyRenderer: appVersion => {
@@ -89,10 +91,17 @@ const detailRecForm = hoistCmp.factory<ActivityDetailModel>(({model}) => {
89
91
  return `${appVersion} (${appEnvironment})`;
90
92
  }
91
93
  }),
92
- formField({field: 'msg'}),
93
94
  formField({
94
- field: 'dateCreated',
95
- readonlyRenderer: dateTimeSecRenderer({})
95
+ field: 'url',
96
+ readonlyRenderer: hyperlinkVal
97
+ }),
98
+ formField({
99
+ field: 'instance',
100
+ readonlyRenderer: v => instance.renderer(v, null)
101
+ }),
102
+ formField({
103
+ field: 'correlationId',
104
+ readonlyRenderer: v => correlationId.renderer(v, null)
96
105
  }),
97
106
  formField({
98
107
  field: 'elapsed',
@@ -102,11 +111,9 @@ const detailRecForm = hoistCmp.factory<ActivityDetailModel>(({model}) => {
102
111
  formatConfig: {thousandSeparated: false, mantissa: 0}
103
112
  })
104
113
  }),
105
- formField({field: 'id'}),
106
- formField({field: 'correlationId'}),
107
114
  formField({
108
- field: 'url',
109
- readonlyRenderer: hyperlinkVal
115
+ field: 'dateCreated',
116
+ readonlyRenderer: dateTimeSecRenderer({})
110
117
  }),
111
118
  h3(Icon.desktop(), 'Device / Browser'),
112
119
  formField({field: 'device'}),
@@ -136,4 +143,4 @@ const additionalDataJsonInput = hoistCmp.factory<ActivityDetailModel>(({model})
136
143
 
137
144
  const valOrNa = v => (v != null ? v : naSpan());
138
145
  const naSpan = () => span({item: 'N/A', className: 'xh-text-color-muted'});
139
- const hyperlinkVal = v => a({href: v, item: v, target: '_blank'});
146
+ const hyperlinkVal = v => (v ? a({href: v, item: v, target: '_blank'}) : '-');
@@ -10,4 +10,7 @@ export declare class AppModel extends HoistAppModel {
10
10
  getAppMenuButtonExtraItems(): any[];
11
11
  getTabRoutes(): Route[];
12
12
  createTabs(): TabConfig[];
13
+ /** Open the primary business-facing application, typically 'app'. */
14
+ openPrimaryApp(): void;
15
+ getPrimaryAppCode(): string;
13
16
  }
@@ -51,6 +51,7 @@ import {
51
51
  } from '@xh/hoist/data';
52
52
  import {ColChooserModel as DesktopColChooserModel} from '@xh/hoist/dynamics/desktop';
53
53
  import {ColChooserModel as MobileColChooserModel} from '@xh/hoist/dynamics/mobile';
54
+ import {Icon} from '@xh/hoist/icon';
54
55
  import {action, bindable, makeObservable, observable, when} from '@xh/hoist/mobx';
55
56
  import {wait, waitFor} from '@xh/hoist/promise';
56
57
  import {ExportOptions} from '@xh/hoist/svc/GridExportService';
@@ -635,6 +636,7 @@ export class GridModel extends HoistModel {
635
636
  message: this.restoreDefaultsWarning,
636
637
  confirmProps: {
637
638
  text: 'Yes, restore defaults',
639
+ icon: Icon.reset(),
638
640
  intent: 'primary'
639
641
  }
640
642
  });
@@ -386,6 +386,7 @@ export class ZoneGridModel extends HoistModel {
386
386
  message: this.restoreDefaultsWarning,
387
387
  confirmProps: {
388
388
  text: 'Yes, restore defaults',
389
+ icon: Icon.reset(),
389
390
  intent: 'primary'
390
391
  }
391
392
  });
@@ -51,6 +51,7 @@ export const [RestoreDefaultsButton, restoreDefaultsButton] =
51
51
  onConfirm: () => XH.restoreDefaultsAsync(),
52
52
  confirmProps: {
53
53
  text: 'Yes, restore defaults',
54
+ icon: Icon.reset(),
54
55
  intent: 'primary'
55
56
  }
56
57
  });
@@ -49,6 +49,7 @@ export const [RestoreDefaultsButton, restoreDefaultsButton] =
49
49
  onConfirm: () => XH.restoreDefaultsAsync(),
50
50
  confirmProps: {
51
51
  text: 'Yes, restore defaults',
52
+ icon: Icon.reset(),
52
53
  intent: 'primary'
53
54
  }
54
55
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "68.0.0-SNAPSHOT.1726665376598",
3
+ "version": "68.0.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",