@xh/hoist 72.3.0 → 72.5.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 +25 -6
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +22 -14
- package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +2 -2
- package/admin/tabs/cluster/instances/BaseInstanceModel.ts +1 -29
- package/admin/tabs/cluster/instances/services/DetailsPanel.ts +2 -1
- package/admin/tabs/cluster/instances/websocket/WebSocketColumns.ts +24 -2
- package/admin/tabs/cluster/instances/websocket/WebSocketModel.ts +76 -25
- package/admin/tabs/cluster/instances/websocket/WebSocketPanel.ts +2 -2
- package/admin/tabs/cluster/objects/DetailModel.ts +4 -40
- package/admin/tabs/cluster/objects/DetailPanel.ts +2 -1
- package/appcontainer/AppContainerModel.ts +2 -0
- package/appcontainer/AppStateModel.ts +40 -8
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +1 -1
- package/build/types/admin/tabs/cluster/instances/BaseInstanceModel.d.ts +1 -3
- package/build/types/admin/tabs/cluster/instances/websocket/WebSocketColumns.d.ts +4 -1
- package/build/types/admin/tabs/cluster/instances/websocket/WebSocketModel.d.ts +5 -2
- package/build/types/admin/tabs/cluster/objects/DetailModel.d.ts +1 -3
- package/build/types/appcontainer/AppStateModel.d.ts +2 -0
- package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +7 -0
- package/build/types/core/XH.d.ts +11 -1
- package/build/types/core/types/Interfaces.d.ts +2 -2
- package/build/types/format/FormatDate.d.ts +22 -1
- package/build/types/format/FormatMisc.d.ts +3 -2
- package/build/types/security/Types.d.ts +0 -25
- package/build/types/security/msal/MsalClient.d.ts +42 -4
- package/build/types/svc/ClientHealthService.d.ts +64 -0
- package/build/types/svc/TrackService.d.ts +3 -11
- package/build/types/svc/WebSocketService.d.ts +38 -15
- package/build/types/svc/index.d.ts +1 -0
- package/build/types/utils/js/index.d.ts +0 -1
- package/cmp/viewmanager/ViewManagerModel.ts +10 -1
- package/core/XH.ts +26 -1
- package/core/types/Interfaces.ts +2 -2
- package/data/filter/BaseFilterFieldSpec.ts +6 -2
- package/desktop/appcontainer/AboutDialog.ts +14 -0
- package/desktop/cmp/button/AppMenuButton.ts +1 -1
- package/desktop/cmp/contextmenu/ContextMenu.ts +1 -1
- package/desktop/cmp/viewmanager/ViewMenu.ts +11 -9
- package/format/FormatDate.ts +45 -3
- package/format/FormatMisc.ts +6 -4
- package/kit/onsen/theme.scss +5 -0
- package/mobile/appcontainer/AboutDialog.scss +1 -1
- package/mobile/appcontainer/AboutDialog.ts +24 -1
- package/mobile/cmp/menu/impl/Menu.ts +2 -2
- package/package.json +1 -1
- package/security/Types.ts +0 -27
- package/security/msal/MsalClient.ts +77 -25
- package/svc/ClientHealthService.ts +179 -0
- package/svc/TrackService.ts +9 -69
- package/svc/WebSocketService.ts +74 -33
- package/svc/index.ts +1 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/utils/js/index.ts +0 -1
- package/build/types/utils/js/BrowserUtils.d.ts +0 -41
- package/utils/js/BrowserUtils.ts +0 -103
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ClusterObjectsModel } from '@xh/hoist/admin/tabs/cluster/objects/ClusterObjectsModel';
|
|
2
2
|
import { GridModel } from '@xh/hoist/cmp/grid';
|
|
3
|
-
import { HoistModel
|
|
3
|
+
import { HoistModel } from '@xh/hoist/core';
|
|
4
4
|
import { StoreRecord } from '@xh/hoist/data';
|
|
5
5
|
export declare class DetailModel extends HoistModel {
|
|
6
6
|
parent: ClusterObjectsModel;
|
|
@@ -11,9 +11,7 @@ export declare class DetailModel extends HoistModel {
|
|
|
11
11
|
get instanceName(): string;
|
|
12
12
|
get selectedAdminStats(): any;
|
|
13
13
|
constructor();
|
|
14
|
-
fmtStats(stats: PlainObject): string;
|
|
15
14
|
private updateGridModel;
|
|
16
15
|
private createGridModel;
|
|
17
16
|
private createColSpec;
|
|
18
|
-
private processTimestamps;
|
|
19
17
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HoistModel, LoadSpec, PlainObject, TaskObserver, Thunkable } from '@xh/hoist/core';
|
|
2
2
|
import type { ViewManagerProvider } from '@xh/hoist/core';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
3
4
|
import { ViewInfo } from './ViewInfo';
|
|
4
5
|
import { View } from './View';
|
|
5
6
|
export interface ViewCreateSpec {
|
|
@@ -85,6 +86,11 @@ export interface ViewManagerConfig {
|
|
|
85
86
|
* Optional user-facing display name for describing global views. Defaults to 'global'
|
|
86
87
|
*/
|
|
87
88
|
globalDisplayName?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Optional key to pass a method that returns a customized BlueprintJS `menuItem` for listing
|
|
91
|
+
* views in the ViewManager menu.
|
|
92
|
+
*/
|
|
93
|
+
viewMenuItemFn?: (view: ViewInfo, model: ViewManagerModel) => ReactNode;
|
|
88
94
|
}
|
|
89
95
|
/**
|
|
90
96
|
* ViewManagerModel coordinates the loading, saving, and management of user-defined bundles of
|
|
@@ -121,6 +127,7 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
121
127
|
readonly instance: string;
|
|
122
128
|
readonly typeDisplayName: string;
|
|
123
129
|
readonly globalDisplayName: string;
|
|
130
|
+
readonly viewMenuItemFn: (view: ViewInfo, model: ViewManagerModel) => ReactNode;
|
|
124
131
|
readonly enableAutoSave: boolean;
|
|
125
132
|
readonly enableDefault: boolean;
|
|
126
133
|
readonly enableGlobal: boolean;
|
package/build/types/core/XH.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RouterModel } from '@xh/hoist/appcontainer/RouterModel';
|
|
2
2
|
import { HoistAuthModel } from '@xh/hoist/core/HoistAuthModel';
|
|
3
3
|
import { Store } from '@xh/hoist/data';
|
|
4
|
-
import { AlertBannerService, AutoRefreshService, ChangelogService, ConfigService, EnvironmentService, FetchOptions, FetchService, GridAutosizeService, GridExportService, IdentityService, IdleService, InspectorService, JsonBlobService, LocalStorageService, PrefService, SessionStorageService, TrackService, WebSocketService } from '@xh/hoist/svc';
|
|
4
|
+
import { AlertBannerService, AutoRefreshService, ChangelogService, ConfigService, EnvironmentService, FetchOptions, FetchService, GridAutosizeService, GridExportService, IdentityService, IdleService, InspectorService, JsonBlobService, LocalStorageService, PrefService, SessionStorageService, TrackService, WebSocketService, ClientHealthService } from '@xh/hoist/svc';
|
|
5
5
|
import { Router, State } from 'router5';
|
|
6
6
|
import { CancelFn } from 'router5/types/types/base';
|
|
7
7
|
import { SetOptional } from 'type-fest';
|
|
@@ -21,6 +21,13 @@ export declare const MIN_HOIST_CORE_VERSION = "28.0";
|
|
|
21
21
|
* Available via import as `XH` - also installed as `window.XH` for troubleshooting purposes.
|
|
22
22
|
*/
|
|
23
23
|
export declare class XHApi {
|
|
24
|
+
/** Unique id for this loaded instance of the app. Unique for every refresh of document. */
|
|
25
|
+
loadId: string;
|
|
26
|
+
/**
|
|
27
|
+
* Unique id for this browser tab/window on this domain.
|
|
28
|
+
* Corresponds to the scope of the built-in sessionStorage object.
|
|
29
|
+
*/
|
|
30
|
+
tabId: string;
|
|
24
31
|
/** Core implementation model hosting all application state. */
|
|
25
32
|
appContainerModel: AppContainerModel;
|
|
26
33
|
/** Provider of centralized exception handling for the app. */
|
|
@@ -47,6 +54,7 @@ export declare class XHApi {
|
|
|
47
54
|
alertBannerService: AlertBannerService;
|
|
48
55
|
autoRefreshService: AutoRefreshService;
|
|
49
56
|
changelogService: ChangelogService;
|
|
57
|
+
clientHealthService: ClientHealthService;
|
|
50
58
|
configService: ConfigService;
|
|
51
59
|
environmentService: EnvironmentService;
|
|
52
60
|
fetchService: FetchService;
|
|
@@ -404,6 +412,8 @@ export declare class XHApi {
|
|
|
404
412
|
*/
|
|
405
413
|
genId(): string;
|
|
406
414
|
private get acm();
|
|
415
|
+
private genLoadId;
|
|
416
|
+
private genTabId;
|
|
407
417
|
}
|
|
408
418
|
/** The app-wide singleton instance. */
|
|
409
419
|
export declare const XH: XHApi;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RuleLike } from '@xh/hoist/data';
|
|
2
|
-
import { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import { MouseEvent, ReactElement, ReactNode } from 'react';
|
|
3
3
|
import { LoadSpec } from '../load';
|
|
4
4
|
import { Intent, PlainObject, Thunkable } from './Types';
|
|
5
5
|
/**
|
|
@@ -226,7 +226,7 @@ export interface MenuItem {
|
|
|
226
226
|
/** Css class name to be added when rendering the menu item. */
|
|
227
227
|
className?: string;
|
|
228
228
|
/** Executed when the user clicks the menu item. */
|
|
229
|
-
actionFn?: () => void;
|
|
229
|
+
actionFn?: (e: MouseEvent | PointerEvent) => void;
|
|
230
230
|
/** Executed before the item is shown. Use to adjust properties dynamically. */
|
|
231
231
|
prepareFn?: (me: MenuItem) => void;
|
|
232
232
|
/** Child menu items. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { DateLike } from '../core/types/Types';
|
|
2
|
+
import { DateLike, PlainObject } from '../core/types/Types';
|
|
3
3
|
import { FormatOptions } from './FormatMisc';
|
|
4
4
|
export declare const DATE_FMT = "YYYY-MM-DD", DATETIME_FMT = "YYYY-MM-DD h:mma", DATETIMESEC_FMT = "YYYY-MM-DD h:mm:ssa", TIME_FMT = "h:mma", MONTH_DAY_FMT = "MMM D";
|
|
5
5
|
/**
|
|
@@ -49,4 +49,25 @@ export interface CompactDateFormatOptions extends FormatOptions<DateLike> {
|
|
|
49
49
|
* Render dates formatted based on distance in time from current day.
|
|
50
50
|
*/
|
|
51
51
|
export declare function fmtCompactDate(v: DateLike, opts?: CompactDateFormatOptions): ReactNode;
|
|
52
|
+
export interface TimestampReplacerConfig {
|
|
53
|
+
/**
|
|
54
|
+
* Suffixes used to identify keys that may hold timestamps.
|
|
55
|
+
* Defaults to ['time', 'date', 'timestamp']
|
|
56
|
+
*/
|
|
57
|
+
suffixes?: string[];
|
|
58
|
+
/**
|
|
59
|
+
* Format for replaced timestamp.
|
|
60
|
+
* Defaults to 'MMM DD HH:mm:ss.SSS'
|
|
61
|
+
*/
|
|
62
|
+
format?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Replace timestamps in an Object with formatted strings.
|
|
66
|
+
*/
|
|
67
|
+
export declare function withFormattedTimestamps(obj: PlainObject, config?: TimestampReplacerConfig): PlainObject;
|
|
68
|
+
/**
|
|
69
|
+
* Create a replacer, suitable for JSON.stringify, that will replace timestamps with
|
|
70
|
+
* formatted strings.
|
|
71
|
+
*/
|
|
72
|
+
export declare function timestampReplacer(config?: TimestampReplacerConfig): (k: string, v: any) => any;
|
|
52
73
|
export declare const dateRenderer: (obj?: string | DateFormatOptions) => (v: DateLike) => ReactNode, dateTimeRenderer: (obj?: string | DateFormatOptions) => (v: any) => ReactNode, dateTimeSecRenderer: (obj?: string | DateFormatOptions) => (v: DateLike) => ReactNode, timeRenderer: (obj?: string | DateFormatOptions) => (v: DateLike) => ReactNode, compactDateRenderer: (obj?: CompactDateFormatOptions) => (v: DateLike) => ReactNode;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CSSProperties, ReactNode } from 'react';
|
|
2
|
+
import { PlainObject } from '@xh/hoist/core';
|
|
2
3
|
export interface FormatOptions<T = any> {
|
|
3
4
|
/** Display value for null values. */
|
|
4
5
|
nullDisplay?: ReactNode;
|
|
@@ -32,9 +33,9 @@ export interface JSONFormatOptions {
|
|
|
32
33
|
space?: number | string;
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
|
-
* Pretty-print a JSON string, adding line breaks and indentation.
|
|
36
|
+
* Pretty-print a JSON string or (JSON Object), adding line breaks and indentation.
|
|
36
37
|
*/
|
|
37
|
-
export declare function fmtJson(
|
|
38
|
+
export declare function fmtJson(v: string | PlainObject, opts?: JSONFormatOptions): string;
|
|
38
39
|
/**
|
|
39
40
|
* Basic util for splitting a string (via ' ') and capitalizing each word - e.g. for names.
|
|
40
41
|
* Not intended to handle more advanced usages such as HTML or other word boundary characters.
|
|
@@ -12,28 +12,3 @@ export interface AccessTokenSpec {
|
|
|
12
12
|
scopes: string[];
|
|
13
13
|
}
|
|
14
14
|
export type TokenMap = Record<string, Token>;
|
|
15
|
-
/** Aggregated telemetry results, produced by {@link MsalClient} when enabled via config. */
|
|
16
|
-
export interface TelemetryResults {
|
|
17
|
-
/** Stats by event type - */
|
|
18
|
-
events: Record<string, TelemetryEventResults>;
|
|
19
|
-
}
|
|
20
|
-
/** Aggregated telemetry results for a single type of event. */
|
|
21
|
-
export interface TelemetryEventResults {
|
|
22
|
-
firstTime: Date;
|
|
23
|
-
lastTime: Date;
|
|
24
|
-
successCount: number;
|
|
25
|
-
failureCount: number;
|
|
26
|
-
/** Timing info (in ms) for event instances reported with duration. */
|
|
27
|
-
duration: {
|
|
28
|
-
count: number;
|
|
29
|
-
total: number;
|
|
30
|
-
average: number;
|
|
31
|
-
worst: number;
|
|
32
|
-
};
|
|
33
|
-
lastFailure?: {
|
|
34
|
-
time: Date;
|
|
35
|
-
duration: number;
|
|
36
|
-
code: string;
|
|
37
|
-
name: string;
|
|
38
|
-
};
|
|
39
|
-
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as msal from '@azure/msal-browser';
|
|
2
2
|
import { LogLevel } from '@azure/msal-browser';
|
|
3
|
+
import { PlainObject } from '@xh/hoist/core';
|
|
3
4
|
import { Token } from '@xh/hoist/security/Token';
|
|
4
5
|
import { BaseOAuthClient, BaseOAuthClientConfig } from '../BaseOAuthClient';
|
|
5
|
-
import { AccessTokenSpec,
|
|
6
|
+
import { AccessTokenSpec, TokenMap } from '../Types';
|
|
6
7
|
export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
|
|
7
8
|
/**
|
|
8
9
|
* Authority for your organization's tenant: `https://login.microsoftonline.com/[tenantId]`.
|
|
@@ -18,8 +19,7 @@ export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
|
|
|
18
19
|
domainHint?: string;
|
|
19
20
|
/**
|
|
20
21
|
* True to enable support for built-in telemetry provided by this class's internal MSAL client.
|
|
21
|
-
* Captured performance events will be summarized
|
|
22
|
-
* See https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/performance.md
|
|
22
|
+
* Captured performance events will be summarized as {@link MsalClientTelemetry}.
|
|
23
23
|
*/
|
|
24
24
|
enableTelemetry?: boolean;
|
|
25
25
|
/**
|
|
@@ -87,7 +87,7 @@ export declare class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTo
|
|
|
87
87
|
private account;
|
|
88
88
|
private initialTokenLoad;
|
|
89
89
|
/** Enable telemetry via `enableTelemetry` ctor config, or via {@link enableTelemetry}. */
|
|
90
|
-
|
|
90
|
+
telemetry: MsalClientTelemetry;
|
|
91
91
|
private _telemetryCbHandle;
|
|
92
92
|
constructor(config: MsalClientConfig);
|
|
93
93
|
protected doInitAsync(): Promise<TokenMap>;
|
|
@@ -96,6 +96,7 @@ export declare class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTo
|
|
|
96
96
|
protected fetchIdTokenAsync(useCache?: boolean): Promise<Token>;
|
|
97
97
|
protected fetchAccessTokenAsync(spec: MsalTokenSpec, useCache?: boolean): Promise<Token>;
|
|
98
98
|
protected doLogoutAsync(): Promise<void>;
|
|
99
|
+
getFormattedTelemetry(): PlainObject;
|
|
99
100
|
enableTelemetry(): void;
|
|
100
101
|
disableTelemetry(): void;
|
|
101
102
|
private loginSsoAsync;
|
|
@@ -106,3 +107,40 @@ export declare class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTo
|
|
|
106
107
|
private get refreshOffsetArgs();
|
|
107
108
|
private noteUserAuthenticated;
|
|
108
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Telemetry produced by this client (if enabled) + included in {@link ClientHealthService}
|
|
112
|
+
* reporting. Leverages MSAL's opt-in support for emitting performance events.
|
|
113
|
+
* See https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/performance.md
|
|
114
|
+
*/
|
|
115
|
+
interface MsalClientTelemetry {
|
|
116
|
+
/** Stats across all events */
|
|
117
|
+
summary: {
|
|
118
|
+
successCount: number;
|
|
119
|
+
failureCount: number;
|
|
120
|
+
maxDuration: number;
|
|
121
|
+
lastFailureTime: number;
|
|
122
|
+
};
|
|
123
|
+
/** Stats by event type */
|
|
124
|
+
events: Record<string, MsalEventTelemetry>;
|
|
125
|
+
}
|
|
126
|
+
/** Aggregated telemetry results for a single type of event. */
|
|
127
|
+
interface MsalEventTelemetry {
|
|
128
|
+
firstTime: number;
|
|
129
|
+
lastTime: number;
|
|
130
|
+
successCount: number;
|
|
131
|
+
failureCount: number;
|
|
132
|
+
/** Timing info (in ms) for event instances reported with duration. */
|
|
133
|
+
duration?: {
|
|
134
|
+
count: number;
|
|
135
|
+
total: number;
|
|
136
|
+
average: number;
|
|
137
|
+
max: number;
|
|
138
|
+
};
|
|
139
|
+
lastFailure?: {
|
|
140
|
+
time: number;
|
|
141
|
+
duration: number;
|
|
142
|
+
code: string;
|
|
143
|
+
name: string;
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { HoistService, PageState, PlainObject } from '@xh/hoist/core';
|
|
2
|
+
import { WebSocketTelemetry } from '@xh/hoist/svc/WebSocketService';
|
|
3
|
+
/**
|
|
4
|
+
* Service for gathering data about the current state and health of the client app, for submission
|
|
5
|
+
* to the server or review on the console during interactive troubleshooting.
|
|
6
|
+
*
|
|
7
|
+
* Hoist sends this data once on application load and can be configured to send at regular intervals
|
|
8
|
+
* throughout a user's session via the `xhActivityTracking.clientHealthReport` app config. Reports
|
|
9
|
+
* are submitted via activity tracking for review within the Admin Console.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ClientHealthService extends HoistService {
|
|
12
|
+
static instance: ClientHealthService;
|
|
13
|
+
private sources;
|
|
14
|
+
initAsync(): Promise<void>;
|
|
15
|
+
get enabled(): boolean;
|
|
16
|
+
/** @returns a customizable report with metrics capturing client app/session state. */
|
|
17
|
+
getReport(): ClientHealthReport;
|
|
18
|
+
/** @returns a report, formatted for easier viewing in console. **/
|
|
19
|
+
getFormattedReport(): PlainObject;
|
|
20
|
+
/**
|
|
21
|
+
* Register a new source for app-specific data to be sent with each report.
|
|
22
|
+
* @param key - key under which to report the data - can be used to remove this source later.
|
|
23
|
+
* @param callback - function returning serializable to include with each report.
|
|
24
|
+
*/
|
|
25
|
+
addSource(key: string, callback: () => any): void;
|
|
26
|
+
/** Unregister a previously-enabled source for client health report data. */
|
|
27
|
+
removeSource(key: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Generate and submit a report to the server, via TrackService.
|
|
30
|
+
*
|
|
31
|
+
* For ad-hoc troubleshooting. Apps may also configure this service to
|
|
32
|
+
* submit on regular intervals.
|
|
33
|
+
*/
|
|
34
|
+
sendReportAsync(): Promise<void>;
|
|
35
|
+
getGeneral(): GeneralData;
|
|
36
|
+
getConnection(): ConnectionData;
|
|
37
|
+
getMemory(): MemoryData;
|
|
38
|
+
getCustom(): PlainObject;
|
|
39
|
+
private sendReportInternal;
|
|
40
|
+
}
|
|
41
|
+
export interface GeneralData {
|
|
42
|
+
startTime: number;
|
|
43
|
+
durationMins: number;
|
|
44
|
+
idleMins: number;
|
|
45
|
+
pageState: PageState;
|
|
46
|
+
}
|
|
47
|
+
export interface ConnectionData {
|
|
48
|
+
downlink: number;
|
|
49
|
+
effectiveType: string;
|
|
50
|
+
rtt: number;
|
|
51
|
+
}
|
|
52
|
+
export interface MemoryData {
|
|
53
|
+
modelCount: number;
|
|
54
|
+
usedPctLimit?: number;
|
|
55
|
+
jsHeapSizeLimit?: number;
|
|
56
|
+
totalJSHeapSize?: number;
|
|
57
|
+
usedJSHeapSize?: number;
|
|
58
|
+
}
|
|
59
|
+
export interface ClientHealthReport {
|
|
60
|
+
general: GeneralData;
|
|
61
|
+
connection: ConnectionData;
|
|
62
|
+
memory: MemoryData;
|
|
63
|
+
webSockets: WebSocketTelemetry;
|
|
64
|
+
}
|
|
@@ -6,7 +6,6 @@ import { HoistService, TrackOptions } from '@xh/hoist/core';
|
|
|
6
6
|
*/
|
|
7
7
|
export declare class TrackService extends HoistService {
|
|
8
8
|
static instance: TrackService;
|
|
9
|
-
private clientHealthReportSources;
|
|
10
9
|
private oncePerSessionSent;
|
|
11
10
|
private pending;
|
|
12
11
|
initAsync(): Promise<void>;
|
|
@@ -15,20 +14,13 @@ export declare class TrackService extends HoistService {
|
|
|
15
14
|
/** Track User Activity. */
|
|
16
15
|
track(options: TrackOptions | string): void;
|
|
17
16
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @param key - key under which to report the data - can be used to remove this source later.
|
|
22
|
-
* @param callback - function returning serializable to include with each report.
|
|
17
|
+
* Flush the queue of pending activity tracking messages to the server.
|
|
18
|
+
* @internal - apps should generally allow this service to manage w/its internal debounce.
|
|
23
19
|
*/
|
|
24
|
-
|
|
25
|
-
/** Unregister a previously-enabled source for client health report data. */
|
|
26
|
-
removeClientHealthReportSource(key: string): void;
|
|
27
|
-
private pushPendingAsync;
|
|
20
|
+
pushPendingAsync(): Promise<void>;
|
|
28
21
|
private pushPendingBuffered;
|
|
29
22
|
private toServerJson;
|
|
30
23
|
private logMessage;
|
|
31
|
-
private sendClientHealthReport;
|
|
32
24
|
}
|
|
33
25
|
interface ActivityTrackingConfig {
|
|
34
26
|
clientHealthReport?: Partial<TrackOptions> & {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HoistService } from '@xh/hoist/core';
|
|
1
|
+
import { HoistService, PlainObject } from '@xh/hoist/core';
|
|
2
2
|
/**
|
|
3
3
|
* Establishes and maintains a websocket connection to the Hoist server, if enabled via `AppSpec`.
|
|
4
4
|
*
|
|
@@ -27,6 +27,10 @@ export declare class WebSocketService extends HoistService {
|
|
|
27
27
|
readonly HEARTBEAT_TOPIC = "xhHeartbeat";
|
|
28
28
|
readonly REG_SUCCESS_TOPIC = "xhRegistrationSuccess";
|
|
29
29
|
readonly FORCE_APP_SUSPEND_TOPIC = "xhForceAppSuspend";
|
|
30
|
+
readonly REQ_CLIENT_HEALTH_RPT_TOPIC = "xhRequestClientHealthReport";
|
|
31
|
+
readonly METADATA_FOR_HANDSHAKE: string[];
|
|
32
|
+
/** True if WebSockets generally enabled - set statically in code via {@link AppSpec}. */
|
|
33
|
+
enabled: boolean;
|
|
30
34
|
/** Unique channel assigned by server upon successful connection. */
|
|
31
35
|
channelKey: string;
|
|
32
36
|
/** Last time a message was received, including heartbeat messages. */
|
|
@@ -35,10 +39,10 @@ export declare class WebSocketService extends HoistService {
|
|
|
35
39
|
get connected(): boolean;
|
|
36
40
|
/** Set to true to log all sent/received messages - very chatty. */
|
|
37
41
|
logMessages: boolean;
|
|
42
|
+
telemetry: WebSocketTelemetry;
|
|
38
43
|
private _timer;
|
|
39
44
|
private _socket;
|
|
40
45
|
private _subsByTopic;
|
|
41
|
-
enabled: boolean;
|
|
42
46
|
constructor();
|
|
43
47
|
initAsync(): Promise<void>;
|
|
44
48
|
/**
|
|
@@ -61,23 +65,24 @@ export declare class WebSocketService extends HoistService {
|
|
|
61
65
|
* Send a message back to the server via the connected websocket.
|
|
62
66
|
*/
|
|
63
67
|
sendMessage(message: WebSocketMessage): void;
|
|
64
|
-
connect(): void;
|
|
65
|
-
disconnect(): void;
|
|
66
|
-
heartbeatOrReconnect(): void;
|
|
67
|
-
private onServerInstanceChange;
|
|
68
68
|
shutdown(): void;
|
|
69
|
+
getFormattedTelemetry(): PlainObject;
|
|
70
|
+
private connect;
|
|
71
|
+
private disconnect;
|
|
72
|
+
private heartbeatOrReconnect;
|
|
73
|
+
private onServerInstanceChange;
|
|
69
74
|
onOpen(ev: any): void;
|
|
70
75
|
onClose(ev: any): void;
|
|
71
76
|
onError(ev: any): void;
|
|
72
|
-
onMessage(rawMsg:
|
|
73
|
-
notifySubscribers
|
|
74
|
-
getSubsForTopic
|
|
75
|
-
updateConnectedStatus
|
|
76
|
-
installChannelKey
|
|
77
|
-
updateLastMessageTime
|
|
78
|
-
buildWebSocketUrl
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
onMessage(rawMsg: MessageEvent): void;
|
|
78
|
+
private notifySubscribers;
|
|
79
|
+
private getSubsForTopic;
|
|
80
|
+
private updateConnectedStatus;
|
|
81
|
+
private installChannelKey;
|
|
82
|
+
private updateLastMessageTime;
|
|
83
|
+
private buildWebSocketUrl;
|
|
84
|
+
private maybeLogMessage;
|
|
85
|
+
private noteTelemetryEvent;
|
|
81
86
|
}
|
|
82
87
|
/**
|
|
83
88
|
* Wrapper class to encapsulate and manage a subscription to messages for a given topic + handler.
|
|
@@ -93,3 +98,21 @@ export interface WebSocketMessage {
|
|
|
93
98
|
topic: string;
|
|
94
99
|
data?: any;
|
|
95
100
|
}
|
|
101
|
+
/** Telemetry collected by this service + included in {@link ClientHealthService} reporting. */
|
|
102
|
+
export interface WebSocketTelemetry {
|
|
103
|
+
channelKey: string;
|
|
104
|
+
subscriptionCount: number;
|
|
105
|
+
events: {
|
|
106
|
+
connOpened?: WebSocketEventTelemetry;
|
|
107
|
+
connClosed?: WebSocketEventTelemetry;
|
|
108
|
+
connError?: WebSocketEventTelemetry;
|
|
109
|
+
msgReceived?: WebSocketEventTelemetry;
|
|
110
|
+
msgSent?: WebSocketEventTelemetry;
|
|
111
|
+
heartbeatReconnectAttempt?: WebSocketEventTelemetry;
|
|
112
|
+
instanceChangeReconnectAttempt?: WebSocketEventTelemetry;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export interface WebSocketEventTelemetry {
|
|
116
|
+
count: number;
|
|
117
|
+
lastTime: number;
|
|
118
|
+
}
|
|
@@ -13,5 +13,6 @@ export * from './JsonBlobService';
|
|
|
13
13
|
export * from './PrefService';
|
|
14
14
|
export * from './TrackService';
|
|
15
15
|
export * from './WebSocketService';
|
|
16
|
+
export * from './ClientHealthService';
|
|
16
17
|
export * from './storage/LocalStorageService';
|
|
17
18
|
export * from './storage/SessionStorageService';
|
|
@@ -123,6 +123,12 @@ export interface ViewManagerConfig {
|
|
|
123
123
|
* Optional user-facing display name for describing global views. Defaults to 'global'
|
|
124
124
|
*/
|
|
125
125
|
globalDisplayName?: string;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Optional key to pass a method that returns a customized BlueprintJS `menuItem` for listing
|
|
129
|
+
* views in the ViewManager menu.
|
|
130
|
+
*/
|
|
131
|
+
viewMenuItemFn?: (view: ViewInfo, model: ViewManagerModel) => ReactNode;
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
/**
|
|
@@ -165,6 +171,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
165
171
|
readonly instance: string;
|
|
166
172
|
readonly typeDisplayName: string;
|
|
167
173
|
readonly globalDisplayName: string;
|
|
174
|
+
readonly viewMenuItemFn: (view: ViewInfo, model: ViewManagerModel) => ReactNode;
|
|
168
175
|
readonly enableAutoSave: boolean;
|
|
169
176
|
readonly enableDefault: boolean;
|
|
170
177
|
readonly enableGlobal: boolean;
|
|
@@ -283,6 +290,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
283
290
|
instance = 'default',
|
|
284
291
|
typeDisplayName,
|
|
285
292
|
globalDisplayName = 'global',
|
|
293
|
+
viewMenuItemFn,
|
|
286
294
|
manageGlobal = false,
|
|
287
295
|
enableAutoSave = true,
|
|
288
296
|
enableDefault = true,
|
|
@@ -296,13 +304,14 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
296
304
|
|
|
297
305
|
throwIf(
|
|
298
306
|
!enableDefault && !initialViewSpec,
|
|
299
|
-
"ViewManagerModel requires 'initialViewSpec' if
|
|
307
|
+
"ViewManagerModel requires 'initialViewSpec' if 'enableDefault' is false."
|
|
300
308
|
);
|
|
301
309
|
|
|
302
310
|
this.type = type;
|
|
303
311
|
this.instance = instance;
|
|
304
312
|
this.typeDisplayName = lowerCase(typeDisplayName ?? genDisplayName(type));
|
|
305
313
|
this.globalDisplayName = globalDisplayName;
|
|
314
|
+
this.viewMenuItemFn = viewMenuItemFn;
|
|
306
315
|
this.manageGlobal = executeIfFunction(manageGlobal) ?? false;
|
|
307
316
|
this.enableDefault = enableDefault;
|
|
308
317
|
this.enableGlobal = enableGlobal;
|
package/core/XH.ts
CHANGED
|
@@ -28,7 +28,8 @@ import {
|
|
|
28
28
|
PrefService,
|
|
29
29
|
SessionStorageService,
|
|
30
30
|
TrackService,
|
|
31
|
-
WebSocketService
|
|
31
|
+
WebSocketService,
|
|
32
|
+
ClientHealthService
|
|
32
33
|
} from '@xh/hoist/svc';
|
|
33
34
|
import {camelCase, flatten, isString, uniqueId} from 'lodash';
|
|
34
35
|
import {Router, State} from 'router5';
|
|
@@ -64,6 +65,7 @@ import {
|
|
|
64
65
|
import {installServicesAsync} from './impl/InstallServices';
|
|
65
66
|
import {instanceManager} from './impl/InstanceManager';
|
|
66
67
|
import {HoistModel, ModelSelector, RefreshContextModel} from './model';
|
|
68
|
+
import ShortUniqueId from 'short-unique-id';
|
|
67
69
|
|
|
68
70
|
export const MIN_HOIST_CORE_VERSION = '28.0';
|
|
69
71
|
|
|
@@ -84,6 +86,15 @@ declare const xhIsDevelopmentMode: boolean;
|
|
|
84
86
|
* Available via import as `XH` - also installed as `window.XH` for troubleshooting purposes.
|
|
85
87
|
*/
|
|
86
88
|
export class XHApi {
|
|
89
|
+
/** Unique id for this loaded instance of the app. Unique for every refresh of document. */
|
|
90
|
+
loadId: string = this.genLoadId();
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Unique id for this browser tab/window on this domain.
|
|
94
|
+
* Corresponds to the scope of the built-in sessionStorage object.
|
|
95
|
+
*/
|
|
96
|
+
tabId: string = this.genTabId();
|
|
97
|
+
|
|
87
98
|
//--------------------------
|
|
88
99
|
// Implementation Delegates
|
|
89
100
|
//--------------------------
|
|
@@ -131,6 +142,7 @@ export class XHApi {
|
|
|
131
142
|
alertBannerService: AlertBannerService;
|
|
132
143
|
autoRefreshService: AutoRefreshService;
|
|
133
144
|
changelogService: ChangelogService;
|
|
145
|
+
clientHealthService: ClientHealthService;
|
|
134
146
|
configService: ConfigService;
|
|
135
147
|
environmentService: EnvironmentService;
|
|
136
148
|
fetchService: FetchService;
|
|
@@ -794,6 +806,19 @@ export class XHApi {
|
|
|
794
806
|
private get acm(): AppContainerModel {
|
|
795
807
|
return this.appContainerModel;
|
|
796
808
|
}
|
|
809
|
+
|
|
810
|
+
private genLoadId(): string {
|
|
811
|
+
return new ShortUniqueId({length: 8}).rnd();
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
private genTabId(): string {
|
|
815
|
+
let ret = window.sessionStorage?.getItem('xhTabId');
|
|
816
|
+
if (!ret) {
|
|
817
|
+
ret = new ShortUniqueId({length: 8}).rnd();
|
|
818
|
+
window.sessionStorage?.setItem('xhTabId', ret);
|
|
819
|
+
}
|
|
820
|
+
return ret;
|
|
821
|
+
}
|
|
797
822
|
}
|
|
798
823
|
|
|
799
824
|
/** The app-wide singleton instance. */
|
package/core/types/Interfaces.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {RuleLike} from '@xh/hoist/data';
|
|
9
|
-
import {ReactElement, ReactNode} from 'react';
|
|
9
|
+
import {MouseEvent, ReactElement, ReactNode} from 'react';
|
|
10
10
|
import {LoadSpec} from '../load';
|
|
11
11
|
import {Intent, PlainObject, Thunkable} from './Types';
|
|
12
12
|
|
|
@@ -284,7 +284,7 @@ export interface MenuItem {
|
|
|
284
284
|
className?: string;
|
|
285
285
|
|
|
286
286
|
/** Executed when the user clicks the menu item. */
|
|
287
|
-
actionFn?: () => void;
|
|
287
|
+
actionFn?: (e: MouseEvent | PointerEvent) => void;
|
|
288
288
|
|
|
289
289
|
/** Executed before the item is shown. Use to adjust properties dynamically. */
|
|
290
290
|
prepareFn?: (me: MenuItem) => void;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import {HoistBase} from '@xh/hoist/core';
|
|
8
8
|
import {Field, Store, FieldFilter, FieldType, genDisplayName, View} from '@xh/hoist/data';
|
|
9
|
-
import {isEmpty} from 'lodash';
|
|
9
|
+
import {compact, isArray, isEmpty} from 'lodash';
|
|
10
10
|
import {FieldFilterOperator} from './Types';
|
|
11
11
|
|
|
12
12
|
export interface BaseFilterFieldSpecConfig {
|
|
@@ -72,7 +72,11 @@ export abstract class BaseFilterFieldSpec extends HoistBase {
|
|
|
72
72
|
this.displayName = displayName ?? sourceField?.displayName ?? genDisplayName(field);
|
|
73
73
|
this.ops = this.parseOperators(ops);
|
|
74
74
|
this.forceSelection = forceSelection ?? false;
|
|
75
|
-
this.values = values
|
|
75
|
+
this.values = isArray(values)
|
|
76
|
+
? compact(values)
|
|
77
|
+
: this.isBoolFieldType
|
|
78
|
+
? [true, false]
|
|
79
|
+
: null;
|
|
76
80
|
this.hasExplicitValues = !isEmpty(this.values);
|
|
77
81
|
this.enableValues = this.hasExplicitValues || (enableValues ?? this.isEnumerableByDefault);
|
|
78
82
|
}
|
|
@@ -42,10 +42,24 @@ export const aboutDialog = hoistCmp.factory({
|
|
|
42
42
|
item: model.getTable()
|
|
43
43
|
}),
|
|
44
44
|
toolbar(
|
|
45
|
+
button({
|
|
46
|
+
text: 'Send Client Health Report',
|
|
47
|
+
icon: Icon.health(),
|
|
48
|
+
omit: !XH.clientHealthService.enabled,
|
|
49
|
+
onClick: async () => {
|
|
50
|
+
try {
|
|
51
|
+
await XH.clientHealthService.sendReportAsync();
|
|
52
|
+
XH.successToast('Client health report successfully submitted.');
|
|
53
|
+
} catch (e) {
|
|
54
|
+
XH.handleException('Error sending client health report', e);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}),
|
|
45
58
|
filler(),
|
|
46
59
|
button({
|
|
47
60
|
text: 'Close',
|
|
48
61
|
intent: 'primary',
|
|
62
|
+
outlined: true,
|
|
49
63
|
onClick: onClose
|
|
50
64
|
})
|
|
51
65
|
)
|
|
@@ -202,7 +202,7 @@ function parseMenuItems(items: MenuItemLike[]): ReactNode[] {
|
|
|
202
202
|
icon: item.icon,
|
|
203
203
|
intent: item.intent,
|
|
204
204
|
className: item.className,
|
|
205
|
-
onClick: actionFn ?
|
|
205
|
+
onClick: actionFn ? e => wait().then(() => actionFn(e)) : null, // do async to allow menu to close
|
|
206
206
|
disabled: item.disabled
|
|
207
207
|
};
|
|
208
208
|
|
|
@@ -70,7 +70,7 @@ function parseItems(items: MenuItemLike[]): ReactNode[] {
|
|
|
70
70
|
icon: item.icon,
|
|
71
71
|
intent: item.intent,
|
|
72
72
|
className: item.className,
|
|
73
|
-
onClick: item.actionFn ?
|
|
73
|
+
onClick: item.actionFn ? e => wait().then(() => item.actionFn(e)) : null, // do async to allow menu to close
|
|
74
74
|
popoverProps: {usePortal: true},
|
|
75
75
|
disabled: item.disabled,
|
|
76
76
|
items
|