From 99f1619fa14f80282f24f71c8c4b4e6323449a85 Mon Sep 17 00:00:00 2001 From: lbwexler Date: Thu, 5 Sep 2024 22:54:27 -0400 Subject: [PATCH 1/4] Consolidate transport mechanism for AlertBanner --- CHANGELOG.md | 6 ++++ .../general/alertBanner/AlertBannerModel.ts | 16 ++++----- .../general/alertBanner/AlertBannerPanel.ts | 8 ++--- svc/AlertBannerService.ts | 36 ++++--------------- svc/EnvironmentService.ts | 12 ++++--- 5 files changed, 31 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 652ff936cc..27f7af692b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## 68.0.0-SNAPSHOT - unreleased +### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW - Hoist Core update only) +* Requires `hoist-core >= 21.1` + +### ⚙️ Technical +* Alert Banner is now updated via the shared environment polling for improved simplicity and frequency. + ## 67.0.0 - 2024-09-03 ### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW - Hoist Core update only) diff --git a/admin/tabs/general/alertBanner/AlertBannerModel.ts b/admin/tabs/general/alertBanner/AlertBannerModel.ts index 9f46a1b49f..2dafbc5bd3 100644 --- a/admin/tabs/general/alertBanner/AlertBannerModel.ts +++ b/admin/tabs/general/alertBanner/AlertBannerModel.ts @@ -188,10 +188,13 @@ export class AlertBannerModel extends HoistModel { } } - async saveBannerSpecAsync(spec: AlertBannerSpec) { + //---------------- + // Implementation + //---------------- + private async saveBannerSpecAsync(spec: AlertBannerSpec): Promise { const {active, message, intent, iconName, enableClose, clientApps} = spec; try { - await XH.fetchService + return await XH.fetchService .postJson({ url: 'alertBannerAdmin/setAlertSpec', body: spec @@ -207,9 +210,6 @@ export class AlertBannerModel extends HoistModel { } } - //---------------- - // Implementation - //---------------- @action private syncPreview() { const vals = this.formModel.values, @@ -233,7 +233,7 @@ export class AlertBannerModel extends HoistModel { let preservedPublishDate = null; // Ask some questions if we are dealing with live stuff - if (XH.alertBannerService.enabled && (active || savedValue?.active)) { + if (active || savedValue?.active) { // Question 1. Reshow when modifying an active && already active, closable banner? if ( active && @@ -299,8 +299,8 @@ export class AlertBannerModel extends HoistModel { updatedBy: XH.getUsername() }; - await this.saveBannerSpecAsync(value); - await XH.alertBannerService.checkForBannerAsync(); + const newSpec = await this.saveBannerSpecAsync(value); + await XH.alertBannerService.updateBanner(newSpec); await this.refreshAsync(); } } diff --git a/admin/tabs/general/alertBanner/AlertBannerPanel.ts b/admin/tabs/general/alertBanner/AlertBannerPanel.ts index 8589449f82..e9a4341ee0 100644 --- a/admin/tabs/general/alertBanner/AlertBannerPanel.ts +++ b/admin/tabs/general/alertBanner/AlertBannerPanel.ts @@ -37,7 +37,7 @@ import {toolbar} from '@xh/hoist/desktop/cmp/toolbar'; import {dateTimeRenderer} from '@xh/hoist/format'; import {Icon} from '@xh/hoist/icon'; import {menu, menuItem, popover} from '@xh/hoist/kit/blueprint'; -import {LocalDate, SECONDS} from '@xh/hoist/utils/datetime'; +import {LocalDate} from '@xh/hoist/utils/datetime'; import {isEmpty} from 'lodash'; import {ReactNode} from 'react'; import {AlertBannerModel} from './AlertBannerModel'; @@ -72,15 +72,13 @@ const formPanel = hoistCmp.factory(({model}) => { labelWidth: 100 }, items: [ - XH.alertBannerService.enabled + XH.getConf('xhAlertBannerConfig', {}).enabled ? div({ className: `${baseClassName}__intro`, items: [ p(`Show an alert banner to all ${XH.appName} users.`), p( - `Configure and preview below. Presets can be saved and loaded via bottom bar menu. Banner will appear to all users within ${ - XH.alertBannerService.interval / SECONDS - }s once marked Active and saved.` + 'Configure and preview below. Presets can be saved and loaded via bottom bar menu. Banner will appear to all users once marked Active and saved.' ) ] }) diff --git a/svc/AlertBannerService.ts b/svc/AlertBannerService.ts index 58a16ce69b..3a2808bf30 100644 --- a/svc/AlertBannerService.ts +++ b/svc/AlertBannerService.ts @@ -6,52 +6,28 @@ */ import {BannerModel} from '@xh/hoist/appcontainer/BannerModel'; import {markdown} from '@xh/hoist/cmp/markdown'; -import {BannerSpec, HoistService, Intent, managed, XH} from '@xh/hoist/core'; +import {BannerSpec, HoistService, Intent, XH} from '@xh/hoist/core'; import {Icon} from '@xh/hoist/icon'; -import {Timer} from '@xh/hoist/utils/async'; -import {SECONDS} from '@xh/hoist/utils/datetime'; import {compact, isEmpty, map, trim} from 'lodash'; /** * Service to display an app-wide alert banner, as configured via the Hoist Admin console. * - * For this service to be active, a client-visible `xhAlertBannerConfig` config must be specified - * as `{enabled:true, interval: x}`, where `x` sets this service's polling frequency in seconds. + * Note that the client is provided with updated banner data from the server via + * EnvironmentService, and its regular polling. See 'xhEnvPollConfig' for more information. */ export class AlertBannerService extends HoistService { override xhImpl = true; static instance: AlertBannerService; - @managed - private timer: Timer; - - get interval(): number { - const conf = XH.getConf('xhAlertBannerConfig', {}); - return conf.enabled && conf.interval ? conf.interval * SECONDS : -1; - } - - get enabled(): boolean { - return this.interval > 0; - } - get lastDismissed(): number { return XH.localStorageService.get('xhAlertBanner.lastDismissed'); } - override async initAsync() { - this.timer = Timer.create({ - runFn: () => this.checkForBannerAsync(), - interval: this.interval - }); - } - - async checkForBannerAsync() { - if (!this.enabled) return; - - const data: AlertBannerSpec = await XH.fetchJson({url: 'xh/alertBanner'}), - {active, expires, publishDate, message, intent, iconName, enableClose, clientApps} = - data, + async updateBanner(spec: AlertBannerSpec) { + const {active, expires, publishDate, message, intent, iconName, enableClose, clientApps} = + spec, {lastDismissed, onClose} = this; if ( diff --git a/svc/EnvironmentService.ts b/svc/EnvironmentService.ts index 017e86eff1..6027840e00 100644 --- a/svc/EnvironmentService.ts +++ b/svc/EnvironmentService.ts @@ -18,7 +18,7 @@ import {version as reactVersion} from 'react'; /** * Load and report on the client and server environment, including software versions, timezones, and - * and other technical information. + * other technical information. */ export class EnvironmentService extends HoistService { static instance: EnvironmentService; @@ -49,7 +49,7 @@ export class EnvironmentService extends HoistService { private pollTimer: Timer; override async initAsync() { - const {pollConfig, instanceName, ...serverEnv} = await XH.fetchJson({ + const {pollConfig, instanceName, alertBanner, ...serverEnv} = await XH.fetchJson({ url: 'xh/environment' }), clientTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'Unknown', @@ -81,7 +81,10 @@ export class EnvironmentService extends HoistService { this.pollConfig = pollConfig; this.addReaction({ when: () => XH.appIsRunning, - run: this.startPolling + run: () => { + XH.alertBannerService.updateBanner(alertBanner); + this.startPolling(); + } }); } @@ -135,10 +138,11 @@ export class EnvironmentService extends HoistService { } // Update config/interval, and server info - const {pollConfig, instanceName, appVersion, appBuild} = data; + const {pollConfig, instanceName, alertBanner, appVersion, appBuild} = data; this.pollConfig = pollConfig; this.pollTimer.setInterval(this.pollIntervalMs); this.setServerInfo(instanceName, appVersion, appBuild); + XH.alertBannerService.updateBanner(alertBanner); // Handle version change if (appVersion != XH.getEnv('appVersion') || appBuild != XH.getEnv('appBuild')) { From 0074b10b13ae0228dc6c7e002194aae0f3dd45fd Mon Sep 17 00:00:00 2001 From: lbwexler Date: Thu, 5 Sep 2024 23:22:16 -0400 Subject: [PATCH 2/4] Consolidate transport mechanism for AlertBanner --- .../general/alertBanner/AlertBannerModel.ts | 10 ++--- svc/EnvironmentService.ts | 42 +++++++++++-------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/admin/tabs/general/alertBanner/AlertBannerModel.ts b/admin/tabs/general/alertBanner/AlertBannerModel.ts index 2dafbc5bd3..c9e8be0f94 100644 --- a/admin/tabs/general/alertBanner/AlertBannerModel.ts +++ b/admin/tabs/general/alertBanner/AlertBannerModel.ts @@ -15,7 +15,7 @@ import {AlertBannerSpec} from '@xh/hoist/svc'; import {isEqual, isMatch, sortBy, without} from 'lodash'; export class AlertBannerModel extends HoistModel { - savedValue; + savedValue: AlertBannerSpec; @observable.ref savedPresets: PlainObject[] = []; @managed @@ -191,10 +191,10 @@ export class AlertBannerModel extends HoistModel { //---------------- // Implementation //---------------- - private async saveBannerSpecAsync(spec: AlertBannerSpec): Promise { + private async saveBannerSpecAsync(spec: AlertBannerSpec) { const {active, message, intent, iconName, enableClose, clientApps} = spec; try { - return await XH.fetchService + await XH.fetchService .postJson({ url: 'alertBannerAdmin/setAlertSpec', body: spec @@ -299,8 +299,8 @@ export class AlertBannerModel extends HoistModel { updatedBy: XH.getUsername() }; - const newSpec = await this.saveBannerSpecAsync(value); - await XH.alertBannerService.updateBanner(newSpec); + await this.saveBannerSpecAsync(value); + await XH.environmentService.pollServerAsync(); await this.refreshAsync(); } } diff --git a/svc/EnvironmentService.ts b/svc/EnvironmentService.ts index 6027840e00..2a3c949658 100644 --- a/svc/EnvironmentService.ts +++ b/svc/EnvironmentService.ts @@ -112,23 +112,15 @@ export class EnvironmentService extends HoistService { return checkMaxVersion(this.get('hoistCoreVersion'), version); } - //------------------------------ - // Implementation - //------------------------------ - constructor() { - super(); - makeObservable(this); - } - - private startPolling() { - this.pollTimer = Timer.create({ - runFn: () => this.pollServerAsync(), - interval: this.pollIntervalMs, - delay: true - }); - } - - private async pollServerAsync() { + /** + * Update critical environment information from server. + * + * Not for application use. Intended to be called frequently on a timer, + * and as needed by Hoist. + * + * @internal + */ + async pollServerAsync() { let data; try { data = await XH.fetchJson({url: 'xh/environmentPoll'}); @@ -167,6 +159,22 @@ export class EnvironmentService extends HoistService { } } + //------------------------------ + // Implementation + //------------------------------ + constructor() { + super(); + makeObservable(this); + } + + private startPolling() { + this.pollTimer = Timer.create({ + runFn: () => this.pollServerAsync(), + interval: this.pollIntervalMs, + delay: true + }); + } + @action private setServerInfo(serverInstance: string, serverVersion: string, serverBuild: string) { this.serverInstance = serverInstance; From 3a9b696d9c2afc38b080c6e0c1918471b9e49958 Mon Sep 17 00:00:00 2001 From: lbwexler Date: Sun, 15 Sep 2024 13:28:17 -0400 Subject: [PATCH 3/4] Tweak to stifle mobx warnings --- admin/tabs/general/alertBanner/AlertBannerModel.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/admin/tabs/general/alertBanner/AlertBannerModel.ts b/admin/tabs/general/alertBanner/AlertBannerModel.ts index 5142c5937f..d124852dac 100644 --- a/admin/tabs/general/alertBanner/AlertBannerModel.ts +++ b/admin/tabs/general/alertBanner/AlertBannerModel.ts @@ -10,13 +10,13 @@ import {FormModel} from '@xh/hoist/cmp/form'; import {fragment, p} from '@xh/hoist/cmp/layout'; import {HoistModel, Intent, LoadSpec, managed, PlainObject, XH} from '@xh/hoist/core'; import {dateIs, required} from '@xh/hoist/data'; -import {action, computed, makeObservable, observable} from '@xh/hoist/mobx'; +import {action, bindable, computed, makeObservable, observable} from '@xh/hoist/mobx'; import {AlertBannerSpec} from '@xh/hoist/svc'; import {isEqual, isMatch, sortBy, without} from 'lodash'; export class AlertBannerModel extends HoistModel { savedValue: AlertBannerSpec; - @observable.ref savedPresets: PlainObject[] = []; + @bindable.ref savedPresets: PlainObject[] = []; @managed formModel = new FormModel({ @@ -118,7 +118,6 @@ export class AlertBannerModel extends HoistModel { this.formModel.setValues({...preset, expires: null}); } - @action addPreset() { const {message, intent, iconName, enableClose, clientApps} = this.formModel.values, dateCreated = Date.now(), From c5d7a7bf888ed8278df31acb597b30e0cd7a6adf Mon Sep 17 00:00:00 2001 From: Anselm McClain Date: Tue, 17 Sep 2024 11:20:11 -0700 Subject: [PATCH 4/4] Minor comment / changelog cleanups --- CHANGELOG.md | 6 ++-- .../general/alertBanner/AlertBannerModel.ts | 36 +++++++++---------- svc/EnvironmentService.ts | 6 +--- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dddd9d901c..096570b016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,14 @@ ## 68.0.0-SNAPSHOT - unreleased ### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW - Hoist Core update only) -* Requires `hoist-core >= 21.1` + +* Requires `hoist-core >= 21.1` for consolidated polling of Alert Banner updates (see below). ### ⚙️ Technical * Updated Admin Console's Cluster tab to refresh more frequently. -* Alert Banner is now updated via the shared environment polling for improved simplicity and frequency. +* Consolidated the polling check for Alert Banner updates into existing `EnvironmentService` + polling, avoiding an extra request and improving alert banner responsiveness. ### ⚙️ Typescript API Adjustments diff --git a/admin/tabs/general/alertBanner/AlertBannerModel.ts b/admin/tabs/general/alertBanner/AlertBannerModel.ts index d124852dac..4fe8aebd41 100644 --- a/admin/tabs/general/alertBanner/AlertBannerModel.ts +++ b/admin/tabs/general/alertBanner/AlertBannerModel.ts @@ -190,24 +190,6 @@ export class AlertBannerModel extends HoistModel { //---------------- // Implementation //---------------- - private async saveBannerSpecAsync(spec: AlertBannerSpec) { - const {active, message, intent, iconName, enableClose, clientApps} = spec; - try { - await XH.fetchService.postJson({ - url: 'alertBannerAdmin/setAlertSpec', - body: spec, - track: { - category: 'Audit', - message: 'Updated Alert Banner', - data: {active, message, intent, iconName, enableClose, clientApps}, - logData: ['active'] - } - }); - } catch (e) { - XH.handleException(e); - } - } - @action private syncPreview() { const vals = this.formModel.values, @@ -301,4 +283,22 @@ export class AlertBannerModel extends HoistModel { await XH.environmentService.pollServerAsync(); await this.refreshAsync(); } + + private async saveBannerSpecAsync(spec: AlertBannerSpec) { + const {active, message, intent, iconName, enableClose, clientApps} = spec; + try { + await XH.fetchService.postJson({ + url: 'alertBannerAdmin/setAlertSpec', + body: spec, + track: { + category: 'Audit', + message: 'Updated Alert Banner', + data: {active, message, intent, iconName, enableClose, clientApps}, + logData: ['active'] + } + }); + } catch (e) { + XH.handleException(e); + } + } } diff --git a/svc/EnvironmentService.ts b/svc/EnvironmentService.ts index 2a3c949658..7050c19575 100644 --- a/svc/EnvironmentService.ts +++ b/svc/EnvironmentService.ts @@ -114,11 +114,7 @@ export class EnvironmentService extends HoistService { /** * Update critical environment information from server. - * - * Not for application use. Intended to be called frequently on a timer, - * and as needed by Hoist. - * - * @internal + * @internal - not for app use. Called by `pollTimer` and as needed by Hoist code. */ async pollServerAsync() { let data;