@equinor/fusion-framework-module-app 6.0.3 → 6.1.1
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 +204 -131
- package/dist/esm/AppClient.js +53 -4
- package/dist/esm/AppClient.js.map +1 -1
- package/dist/esm/AppModuleProvider.js +15 -0
- package/dist/esm/AppModuleProvider.js.map +1 -1
- package/dist/esm/app/App.js +157 -5
- package/dist/esm/app/App.js.map +1 -1
- package/dist/esm/app/actions.js +17 -0
- package/dist/esm/app/actions.js.map +1 -1
- package/dist/esm/app/create-reducer.js +3 -0
- package/dist/esm/app/create-reducer.js.map +1 -1
- package/dist/esm/app/create-state.js +4 -1
- package/dist/esm/app/create-state.js.map +1 -1
- package/dist/esm/app/flows.js +47 -1
- package/dist/esm/app/flows.js.map +1 -1
- package/dist/esm/errors.js +30 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/AppClient.d.ts +23 -1
- package/dist/types/AppModuleProvider.d.ts +12 -1
- package/dist/types/app/App.d.ts +58 -2
- package/dist/types/app/actions.d.ts +37 -1
- package/dist/types/app/create-reducer.d.ts +29 -0
- package/dist/types/app/events.d.ts +12 -1
- package/dist/types/app/flows.d.ts +16 -0
- package/dist/types/app/types.d.ts +6 -2
- package/dist/types/errors.d.ts +20 -0
- package/dist/types/types.d.ts +3 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +5 -3
- package/src/AppClient.ts +83 -4
- package/src/AppModuleProvider.ts +18 -1
- package/src/app/App.ts +267 -21
- package/src/app/actions.ts +34 -1
- package/src/app/create-reducer.ts +3 -0
- package/src/app/create-state.ts +12 -1
- package/src/app/events.ts +20 -1
- package/src/app/flows.ts +71 -1
- package/src/app/types.ts +6 -1
- package/src/errors.ts +43 -0
- package/src/types.ts +4 -0
- package/src/version.ts +1 -1
package/src/app/App.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
AppScriptModule,
|
|
4
4
|
AppManifest,
|
|
5
5
|
AppConfig,
|
|
6
|
+
AppSettings,
|
|
6
7
|
ConfigEnvironment,
|
|
7
8
|
} from '../types';
|
|
8
9
|
import { FlowSubject, Observable } from '@equinor/fusion-observable';
|
|
@@ -10,20 +11,22 @@ import { FlowSubject, Observable } from '@equinor/fusion-observable';
|
|
|
10
11
|
import type { AppModuleProvider } from '../AppModuleProvider';
|
|
11
12
|
import {
|
|
12
13
|
combineLatest,
|
|
13
|
-
filter,
|
|
14
|
-
firstValueFrom,
|
|
15
|
-
lastValueFrom,
|
|
16
|
-
map,
|
|
17
14
|
of,
|
|
18
|
-
OperatorFunction,
|
|
15
|
+
type OperatorFunction,
|
|
19
16
|
Subscription,
|
|
17
|
+
firstValueFrom,
|
|
18
|
+
lastValueFrom,
|
|
20
19
|
} from 'rxjs';
|
|
20
|
+
import { defaultIfEmpty, filter, last, map, switchMap } from 'rxjs/operators';
|
|
21
|
+
|
|
21
22
|
import { EventModule } from '@equinor/fusion-framework-module-event';
|
|
22
23
|
import { AnyModule, ModuleType } from '@equinor/fusion-framework-module';
|
|
23
24
|
import { createState } from './create-state';
|
|
24
25
|
import { actions, Actions } from './actions';
|
|
25
26
|
import { AppBundleState, AppBundleStateInitial } from './types';
|
|
26
27
|
|
|
28
|
+
import isEqual from 'fast-deep-equal';
|
|
29
|
+
|
|
27
30
|
import './events';
|
|
28
31
|
|
|
29
32
|
// TODO - move globally
|
|
@@ -65,6 +68,18 @@ export interface IApp<
|
|
|
65
68
|
*/
|
|
66
69
|
get instance$(): Observable<AppModulesInstance<TModules>>;
|
|
67
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Observable that emits the settings of the app.
|
|
73
|
+
* @returns An Observable that emits the app settings.
|
|
74
|
+
*/
|
|
75
|
+
get settings$(): Observable<AppSettings>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Observable that emits the status of the app.
|
|
79
|
+
* @returns An Observable that emits the app status.
|
|
80
|
+
*/
|
|
81
|
+
get status$(): Observable<AppBundleState['status']>;
|
|
82
|
+
|
|
68
83
|
/**
|
|
69
84
|
* Gets the current state of the Application.
|
|
70
85
|
* @returns The current state of the Application.
|
|
@@ -152,6 +167,56 @@ export interface IApp<
|
|
|
152
167
|
*/
|
|
153
168
|
getConfigAsync(allow_cache?: boolean): Promise<AppConfig>;
|
|
154
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Gets the app settings.
|
|
172
|
+
* @param force_refresh Whether to force refreshing the settings.
|
|
173
|
+
* @returns An observable that emits the app settings.
|
|
174
|
+
*/
|
|
175
|
+
getSettings(force_refresh?: boolean): Observable<AppSettings>;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Retrieves the app settings asynchronously.
|
|
179
|
+
* @param allow_cache Whether to allow loading from cache.
|
|
180
|
+
* @returns A promise that resolves to the AppSettings.
|
|
181
|
+
*/
|
|
182
|
+
getSettingsAsync(allow_cache?: boolean): Promise<AppSettings>;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Sets the app settings.
|
|
186
|
+
* @param settings The settings object to save.
|
|
187
|
+
* @returns An observable that emits the app settings.
|
|
188
|
+
*/
|
|
189
|
+
updateSettings<T extends AppSettings>(settings: T): Observable<T>;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Sets the app settings asyncronously.
|
|
193
|
+
* @param settings The settings object to save.
|
|
194
|
+
* @returns An Promise that resolves the app settings.
|
|
195
|
+
*/
|
|
196
|
+
updateSettingsAsync<T extends AppSettings>(settings: T): Promise<T>;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Updates a specific setting of the app.
|
|
200
|
+
* @param property The property to update.
|
|
201
|
+
* @param value The value to set.
|
|
202
|
+
* @returns An observable that emits the app settings.
|
|
203
|
+
*/
|
|
204
|
+
updateSetting<T extends AppSettings, P extends keyof T>(
|
|
205
|
+
property: P,
|
|
206
|
+
value: T[P],
|
|
207
|
+
): Observable<T[P]>;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Updates a specific setting of the app asynchronously.
|
|
211
|
+
* @param property The property to update.
|
|
212
|
+
* @param value The value to set.
|
|
213
|
+
* @returns A promise that resolves to the AppSettings.
|
|
214
|
+
*/
|
|
215
|
+
updateSettingAsync<T extends AppSettings, P extends keyof T>(
|
|
216
|
+
property: P,
|
|
217
|
+
value: T[P],
|
|
218
|
+
): Promise<T[P]>;
|
|
219
|
+
|
|
155
220
|
/**
|
|
156
221
|
* Gets the app manifest.
|
|
157
222
|
* @param force_refresh Whether to force refreshing the manifest.
|
|
@@ -181,6 +246,8 @@ export interface IApp<
|
|
|
181
246
|
getAppModuleAsync(allow_cache?: boolean): Promise<AppScriptModule>;
|
|
182
247
|
}
|
|
183
248
|
|
|
249
|
+
const fallbackSettings: AppSettings = {};
|
|
250
|
+
|
|
184
251
|
// TODO make streams distinct until changed from state
|
|
185
252
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
186
253
|
export class App<
|
|
@@ -193,31 +260,39 @@ export class App<
|
|
|
193
260
|
//#region === streams ===
|
|
194
261
|
|
|
195
262
|
get manifest$(): Observable<AppManifest> {
|
|
196
|
-
return this.#state.pipe(
|
|
197
|
-
map(({ manifest }) => manifest),
|
|
198
|
-
filterEmpty(),
|
|
199
|
-
);
|
|
263
|
+
return this.#state.select((state) => state.manifest).pipe(filterEmpty());
|
|
200
264
|
}
|
|
201
265
|
|
|
202
266
|
get config$(): Observable<AppConfig<TEnv>> {
|
|
203
|
-
return this.#state
|
|
204
|
-
|
|
205
|
-
filterEmpty()
|
|
206
|
-
);
|
|
267
|
+
return this.#state
|
|
268
|
+
.select((state) => state.config as AppConfig<TEnv>, isEqual)
|
|
269
|
+
.pipe(filterEmpty());
|
|
207
270
|
}
|
|
208
271
|
|
|
209
272
|
get modules$(): Observable<AppScriptModule> {
|
|
210
|
-
return this.#state.pipe(
|
|
211
|
-
map(({ modules }) => modules),
|
|
212
|
-
filterEmpty(),
|
|
213
|
-
);
|
|
273
|
+
return this.#state.select((state) => state.modules).pipe(filterEmpty());
|
|
214
274
|
}
|
|
215
275
|
|
|
216
276
|
get instance$(): Observable<AppModulesInstance<TModules>> {
|
|
217
|
-
return this.#state
|
|
218
|
-
|
|
219
|
-
filterEmpty()
|
|
220
|
-
|
|
277
|
+
return this.#state
|
|
278
|
+
.select((state) => state.instance as AppModulesInstance<TModules>)
|
|
279
|
+
.pipe(filterEmpty());
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
get settings$(): Observable<AppSettings> {
|
|
283
|
+
return new Observable<AppSettings>((subscriber) => {
|
|
284
|
+
this.#state.next(actions.fetchSettings(this.appKey));
|
|
285
|
+
subscriber.add(
|
|
286
|
+
this.#state
|
|
287
|
+
.select((state) => state.settings, isEqual)
|
|
288
|
+
.pipe(filterEmpty(), defaultIfEmpty(fallbackSettings))
|
|
289
|
+
.subscribe(subscriber),
|
|
290
|
+
);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
get status$(): Observable<AppBundleState['status']> {
|
|
295
|
+
return this.#state.select((state) => state.status);
|
|
221
296
|
}
|
|
222
297
|
|
|
223
298
|
//#endregion
|
|
@@ -358,6 +433,60 @@ export class App<
|
|
|
358
433
|
});
|
|
359
434
|
});
|
|
360
435
|
|
|
436
|
+
// monitor when application settings is loading
|
|
437
|
+
this.#state.addEffect(actions.fetchSettings.type, () => {
|
|
438
|
+
// dispatch event to notify listeners that the application settings is being loaded
|
|
439
|
+
event.dispatchEvent('onAppSettingsLoad', {
|
|
440
|
+
detail: { appKey },
|
|
441
|
+
source: this,
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// monitor when application settings is loaded
|
|
446
|
+
this.#state.addEffect(actions.fetchSettings.success.type, (action) => {
|
|
447
|
+
// dispatch event to notify listeners that the application settings has been loaded
|
|
448
|
+
event.dispatchEvent('onAppSettingsLoaded', {
|
|
449
|
+
detail: { appKey, settings: action.payload },
|
|
450
|
+
source: this,
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// monitor when application settings fails to load
|
|
455
|
+
this.#state.addEffect(actions.fetchSettings.failure.type, (action) => {
|
|
456
|
+
// dispatch event to notify listeners that the application settings failed to load
|
|
457
|
+
event.dispatchEvent('onAppSettingsFailure', {
|
|
458
|
+
detail: { appKey, error: action.payload },
|
|
459
|
+
source: this,
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// monitor when application settings is updated
|
|
464
|
+
this.#state.addEffect(actions.updateSettings.type, (action) => {
|
|
465
|
+
// dispatch event to notify listeners that the application settings has been loaded
|
|
466
|
+
event.dispatchEvent('onAppSettingsUpdate', {
|
|
467
|
+
detail: { appKey, settings: action.payload.settings },
|
|
468
|
+
source: this,
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// monitor when application settings is updated
|
|
473
|
+
this.#state.addEffect(actions.updateSettings.success.type, (action) => {
|
|
474
|
+
// dispatch event to notify listeners that the application settings has been loaded
|
|
475
|
+
event.dispatchEvent('onAppSettingsUpdated', {
|
|
476
|
+
detail: { appKey, settings: action.payload.settings },
|
|
477
|
+
source: this,
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// monitor when application settings fails to updated
|
|
482
|
+
this.#state.addEffect(actions.updateSettings.failure.type, (action) => {
|
|
483
|
+
// dispatch event to notify listeners that the application settings has been loaded
|
|
484
|
+
event.dispatchEvent('onAppSettingsUpdateFailure', {
|
|
485
|
+
detail: { appKey, settings: action.payload },
|
|
486
|
+
source: this,
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
361
490
|
// monitor when application script is loading
|
|
362
491
|
this.#state.addEffect(actions.importApp.type, () => {
|
|
363
492
|
// dispatch event to notify listeners that the application script is being loaded
|
|
@@ -529,6 +658,123 @@ export class App<
|
|
|
529
658
|
return operator(this.getConfig(!allow_cache));
|
|
530
659
|
}
|
|
531
660
|
|
|
661
|
+
public getSettings<T extends AppSettings>(force_refresh = false): Observable<T> {
|
|
662
|
+
return new Observable<T>((subscriber) => {
|
|
663
|
+
if (this.#state.value.settings) {
|
|
664
|
+
// emit current settings to the subscriber
|
|
665
|
+
subscriber.next(this.#state.value.settings as T);
|
|
666
|
+
if (!force_refresh) {
|
|
667
|
+
// since we have the settings and no force refresh, complete the stream
|
|
668
|
+
return subscriber.complete();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// when stream closes, dispose of subscription to change of state settings
|
|
673
|
+
subscriber.add(
|
|
674
|
+
// monitor changes to state changes of settings and emit to subscriber
|
|
675
|
+
this.#state.addEffect('set_settings', ({ payload }) => {
|
|
676
|
+
subscriber.next(payload as T);
|
|
677
|
+
}),
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
// when stream closes, dispose of subscription to fetch settings
|
|
681
|
+
subscriber.add(
|
|
682
|
+
// monitor success of fetching settings and emit to subscriber
|
|
683
|
+
this.#state.addEffect('fetch_settings::success', ({ payload }) => {
|
|
684
|
+
// application settings loaded, emit to subscriber and complete the stream
|
|
685
|
+
subscriber.next(payload as T);
|
|
686
|
+
subscriber.complete();
|
|
687
|
+
}),
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
// when stream closes, dispose of subscription to fetch settings
|
|
691
|
+
subscriber.add(
|
|
692
|
+
// monitor failure of fetching settings and emit error to subscriber
|
|
693
|
+
this.#state.addEffect('fetch_settings::failure', ({ payload }) => {
|
|
694
|
+
// application settings failed to load, emit error and complete the stream
|
|
695
|
+
subscriber.error(
|
|
696
|
+
Error('failed to load application settings', {
|
|
697
|
+
cause: payload,
|
|
698
|
+
}),
|
|
699
|
+
);
|
|
700
|
+
}),
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
this.#state.next(actions.fetchSettings(this.appKey));
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
public getSettingsAsync<T extends AppSettings>(allow_cache = true): Promise<T> {
|
|
708
|
+
// when allow_cache is true, use first emitted value, otherwise use last emitted value
|
|
709
|
+
const operator = allow_cache ? firstValueFrom : lastValueFrom;
|
|
710
|
+
return operator(this.getSettings<T>(!allow_cache));
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
public updateSettings<T extends AppSettings>(settings: T): Observable<T> {
|
|
714
|
+
return new Observable((subscriber) => {
|
|
715
|
+
subscriber.add(
|
|
716
|
+
// monitor failure of updating settings and emit error to subscriber
|
|
717
|
+
this.#state.addEffect('update_settings::failure', ({ payload }) => {
|
|
718
|
+
// request to reset settings to source state
|
|
719
|
+
this.#state.next(actions.fetchSettings(this.appKey));
|
|
720
|
+
|
|
721
|
+
// application settings failed to save, emit error and complete the stream
|
|
722
|
+
subscriber.error(
|
|
723
|
+
Error('failed to update application settings', {
|
|
724
|
+
cause: payload,
|
|
725
|
+
}),
|
|
726
|
+
);
|
|
727
|
+
}),
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
subscriber.add(
|
|
731
|
+
// monitor success of updating settings and emit to subscriber
|
|
732
|
+
this.#state.addEffect('update_settings::success', ({ payload }) => {
|
|
733
|
+
subscriber.next(payload as T);
|
|
734
|
+
subscriber.complete();
|
|
735
|
+
}),
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
// optimistic update of settings
|
|
739
|
+
this.#state.next(actions.setSettings(settings));
|
|
740
|
+
|
|
741
|
+
// request to update settings
|
|
742
|
+
this.#state.next(actions.updateSettings(this.appKey, settings));
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
public updateSettingsAsync<T extends AppSettings>(settings: T): Promise<T> {
|
|
747
|
+
return lastValueFrom(this.updateSettings(settings));
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
public updateSetting<T extends AppSettings, P extends keyof T>(
|
|
751
|
+
property: P,
|
|
752
|
+
value: T[P],
|
|
753
|
+
): Observable<T[P]> {
|
|
754
|
+
const currentSettings$ =
|
|
755
|
+
this.#state.value.settings === undefined
|
|
756
|
+
? // if settings are not loaded, fetch settings
|
|
757
|
+
this.getSettings().pipe(last())
|
|
758
|
+
: // if settings are loaded, use current settings
|
|
759
|
+
of(this.#state.value.settings);
|
|
760
|
+
|
|
761
|
+
return currentSettings$.pipe(
|
|
762
|
+
// merge current settings with new value
|
|
763
|
+
map((settings) => ({ ...settings, [property]: value })),
|
|
764
|
+
// update settings
|
|
765
|
+
switchMap((settings) => this.updateSettings<T>(settings as T)),
|
|
766
|
+
// return the updated property
|
|
767
|
+
map((settings) => settings[property] as T[P]),
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
public updateSettingAsync<T extends AppSettings, P extends keyof T>(
|
|
772
|
+
property: P,
|
|
773
|
+
value: T[P],
|
|
774
|
+
): Promise<T[P]> {
|
|
775
|
+
return lastValueFrom(this.updateSetting<T, P>(property, value));
|
|
776
|
+
}
|
|
777
|
+
|
|
532
778
|
public getManifest(force_refresh = false): Observable<AppManifest> {
|
|
533
779
|
return new Observable((subscriber) => {
|
|
534
780
|
if (this.#state.value.manifest) {
|
package/src/app/actions.ts
CHANGED
|
@@ -4,7 +4,13 @@ import {
|
|
|
4
4
|
createAction,
|
|
5
5
|
createAsyncAction,
|
|
6
6
|
} from '@equinor/fusion-observable';
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
AppConfig,
|
|
9
|
+
AppManifest,
|
|
10
|
+
AppModulesInstance,
|
|
11
|
+
AppScriptModule,
|
|
12
|
+
AppSettings,
|
|
13
|
+
} from '../types';
|
|
8
14
|
|
|
9
15
|
const createActions = () => ({
|
|
10
16
|
/** Manifest loading */
|
|
@@ -31,6 +37,33 @@ const createActions = () => ({
|
|
|
31
37
|
(config: AppConfig) => ({ payload: config }),
|
|
32
38
|
(error: unknown) => ({ payload: error }),
|
|
33
39
|
),
|
|
40
|
+
/** Settings loading */
|
|
41
|
+
setSettings: createAction('set_settings', (settings?: AppSettings) => ({
|
|
42
|
+
payload: settings,
|
|
43
|
+
})),
|
|
44
|
+
/** Fetching settings */
|
|
45
|
+
fetchSettings: createAsyncAction(
|
|
46
|
+
'fetch_settings',
|
|
47
|
+
(appKey: string) => ({ payload: { appKey } }),
|
|
48
|
+
(settings: AppSettings) => ({ payload: settings }),
|
|
49
|
+
(error: unknown) => ({ payload: error }),
|
|
50
|
+
),
|
|
51
|
+
/** Updating settings */
|
|
52
|
+
updateSettings: createAsyncAction(
|
|
53
|
+
'update_settings',
|
|
54
|
+
(appKey: string, settings: AppSettings) => ({
|
|
55
|
+
payload: { appKey, settings },
|
|
56
|
+
}),
|
|
57
|
+
(settings: AppSettings) => ({
|
|
58
|
+
payload: settings,
|
|
59
|
+
}),
|
|
60
|
+
(error: unknown) => ({
|
|
61
|
+
payload: error,
|
|
62
|
+
}),
|
|
63
|
+
),
|
|
64
|
+
updateSettingsAbort: createAction('update_settings::abort', (id: string) => ({
|
|
65
|
+
payload: id,
|
|
66
|
+
})),
|
|
34
67
|
/** App loading */
|
|
35
68
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
69
|
setModule: createAction('set_module', (module: any) => ({ payload: module })),
|
|
@@ -30,6 +30,9 @@ export const createReducer = (value: AppBundleStateInitial) =>
|
|
|
30
30
|
.addCase(actions.setConfig, (state, action) => {
|
|
31
31
|
state.config = action.payload;
|
|
32
32
|
})
|
|
33
|
+
.addCase(actions.setSettings, (state, action) => {
|
|
34
|
+
state.settings = action.payload;
|
|
35
|
+
})
|
|
33
36
|
.addCase(actions.setModule, (state, action) => {
|
|
34
37
|
state.modules = action.payload;
|
|
35
38
|
})
|
package/src/app/create-state.ts
CHANGED
|
@@ -2,7 +2,13 @@ import { FlowSubject } from '@equinor/fusion-observable';
|
|
|
2
2
|
|
|
3
3
|
import { createReducer } from './create-reducer';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
handleFetchManifest,
|
|
7
|
+
handleFetchConfig,
|
|
8
|
+
handleFetchSettings,
|
|
9
|
+
handleUpdateSettings,
|
|
10
|
+
handleImportApplication,
|
|
11
|
+
} from './flows';
|
|
6
12
|
|
|
7
13
|
import type { Actions } from './actions';
|
|
8
14
|
import type { AppBundleState, AppBundleStateInitial } from './types';
|
|
@@ -23,6 +29,11 @@ export const createState = (
|
|
|
23
29
|
// add handler for fetching config
|
|
24
30
|
state.addFlow(handleFetchConfig(provider));
|
|
25
31
|
|
|
32
|
+
// add handler for fetching settings
|
|
33
|
+
state.addFlow(handleFetchSettings(provider));
|
|
34
|
+
|
|
35
|
+
state.addFlow(handleUpdateSettings(provider));
|
|
36
|
+
|
|
26
37
|
// add handler for loading application script
|
|
27
38
|
state.addFlow(handleImportApplication(provider));
|
|
28
39
|
|
package/src/app/events.ts
CHANGED
|
@@ -2,7 +2,13 @@ import type { FrameworkEvent, FrameworkEventInit } from '@equinor/fusion-framewo
|
|
|
2
2
|
|
|
3
3
|
import type { App } from './App';
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
AppConfig,
|
|
7
|
+
AppManifest,
|
|
8
|
+
AppModulesInstance,
|
|
9
|
+
AppScriptModule,
|
|
10
|
+
AppSettings,
|
|
11
|
+
} from '../types';
|
|
6
12
|
|
|
7
13
|
/** base event type for applications */
|
|
8
14
|
export type AppEventEventInit<TDetail extends Record<string, unknown> | unknown = unknown> =
|
|
@@ -45,6 +51,19 @@ declare module '@equinor/fusion-framework-module-event' {
|
|
|
45
51
|
}>;
|
|
46
52
|
onAppConfigFailure: AppEventFailure;
|
|
47
53
|
|
|
54
|
+
onAppSettingsLoad: AppEvent;
|
|
55
|
+
/** fired when the application has loaded corresponding settings */
|
|
56
|
+
onAppSettingsLoaded: AppEvent<{
|
|
57
|
+
settings: AppSettings;
|
|
58
|
+
}>;
|
|
59
|
+
onAppSettingsFailure: AppEventFailure;
|
|
60
|
+
|
|
61
|
+
onAppSettingsUpdate: AppEvent;
|
|
62
|
+
onAppSettingsUpdated: AppEvent<{
|
|
63
|
+
settings: AppSettings;
|
|
64
|
+
}>;
|
|
65
|
+
onAppSettingsUpdateFailure: AppEventFailure;
|
|
66
|
+
|
|
48
67
|
/** fired when the application has loaded corresponding javascript module */
|
|
49
68
|
onAppScriptLoad: AppEvent;
|
|
50
69
|
onAppScriptLoaded: AppEvent<{
|
package/src/app/flows.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { from, of, concat } from 'rxjs';
|
|
2
|
-
import { catchError, filter, last, map, share, switchMap } from 'rxjs/operators';
|
|
2
|
+
import { catchError, concatMap, filter, last, map, share, switchMap } from 'rxjs/operators';
|
|
3
3
|
|
|
4
4
|
import { actions } from './actions';
|
|
5
5
|
|
|
@@ -93,6 +93,76 @@ export const handleFetchConfig =
|
|
|
93
93
|
}),
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Handles the fetch settings action by fetching the app settings from the provider,
|
|
98
|
+
* filtering out null values, and dispatching success or failure actions accordingly.
|
|
99
|
+
*
|
|
100
|
+
* @param provider The AppModuleProvider used to fetch the app settings.
|
|
101
|
+
* @returns A Flow function that takes an Observable of actions and returns an Observable of actions.
|
|
102
|
+
*/
|
|
103
|
+
export const handleFetchSettings =
|
|
104
|
+
(provider: AppModuleProvider): Flow<Actions, AppBundleState> =>
|
|
105
|
+
(action$) =>
|
|
106
|
+
action$.pipe(
|
|
107
|
+
// only handle fetch settings request actions
|
|
108
|
+
filter(actions.fetchSettings.match),
|
|
109
|
+
// when request is received, abort any ongoing request and start new
|
|
110
|
+
switchMap(({ payload }) => {
|
|
111
|
+
const { appKey } = payload;
|
|
112
|
+
|
|
113
|
+
// fetch settings from provider
|
|
114
|
+
const subject = from(provider.getAppSettings(appKey)).pipe(
|
|
115
|
+
// filter out null values
|
|
116
|
+
filter((x) => !!x),
|
|
117
|
+
// allow multiple subscriptions
|
|
118
|
+
share(),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// first load settings and then dispatch success action
|
|
122
|
+
return concat(
|
|
123
|
+
subject.pipe(map((settings) => actions.setSettings(settings))),
|
|
124
|
+
subject.pipe(
|
|
125
|
+
last(),
|
|
126
|
+
map((settings) => actions.fetchSettings.success(settings)),
|
|
127
|
+
),
|
|
128
|
+
).pipe(
|
|
129
|
+
// catch any error and dispatch failure action
|
|
130
|
+
catchError((err) => {
|
|
131
|
+
return of(actions.fetchSettings.failure(err));
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Handles the set settings action by setting the app settings from the provider,
|
|
139
|
+
* filtering out null values, and dispatching success or failure actions accordingly.
|
|
140
|
+
*
|
|
141
|
+
* @param provider The AppModuleProvider used to fetch the app settings.
|
|
142
|
+
* @returns A Flow function that takes an Observable of actions and returns an Observable of actions.
|
|
143
|
+
*/
|
|
144
|
+
export const handleUpdateSettings =
|
|
145
|
+
(provider: AppModuleProvider): Flow<Actions, AppBundleState> =>
|
|
146
|
+
(action$) => {
|
|
147
|
+
return action$.pipe(filter(actions.updateSettings.match)).pipe(
|
|
148
|
+
switchMap(({ payload }) => {
|
|
149
|
+
const { appKey, settings } = payload;
|
|
150
|
+
return provider.updateAppSettings(appKey, settings).pipe(
|
|
151
|
+
// take the last value
|
|
152
|
+
last(),
|
|
153
|
+
// request updating of settings and dispatch success action
|
|
154
|
+
concatMap((updatedSettings) =>
|
|
155
|
+
from([
|
|
156
|
+
actions.setSettings(updatedSettings),
|
|
157
|
+
actions.updateSettings.success(updatedSettings),
|
|
158
|
+
]),
|
|
159
|
+
),
|
|
160
|
+
catchError((err) => of(actions.updateSettings.failure(err))),
|
|
161
|
+
);
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
96
166
|
/**
|
|
97
167
|
* Handles the import application flow.
|
|
98
168
|
* @returns A flow that takes in actions and returns an observable of AppBundleState.
|
package/src/app/types.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { ActionBaseType } from '@equinor/fusion-observable';
|
|
1
2
|
import type {
|
|
2
3
|
AppManifest,
|
|
3
4
|
AppConfig,
|
|
4
5
|
AppModulesInstance,
|
|
5
6
|
AppScriptModule,
|
|
6
7
|
ConfigEnvironment,
|
|
8
|
+
AppSettings,
|
|
7
9
|
} from '../types';
|
|
10
|
+
import { Actions } from './actions';
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Represents the state of an application bundle.
|
|
@@ -16,6 +19,7 @@ import type {
|
|
|
16
19
|
* @property {Set<string>} status - A set of strings representing the status of the application.
|
|
17
20
|
* @property {AppManifest} [manifest] - An optional manifest describing the application.
|
|
18
21
|
* @property {AppConfig<TConfig>} [config] - An optional configuration object for the application.
|
|
22
|
+
* @property {AppSettings} [settings] - An optional application settings object.
|
|
19
23
|
* @property {AppScriptModule} [modules] - An optional script module for the application.
|
|
20
24
|
* @property {AppModulesInstance<TModules>} [instance] - An optional instance of the application modules.
|
|
21
25
|
*/
|
|
@@ -25,9 +29,10 @@ export type AppBundleState<
|
|
|
25
29
|
TModules = any,
|
|
26
30
|
> = {
|
|
27
31
|
appKey: string;
|
|
28
|
-
status: Set<
|
|
32
|
+
status: Set<ActionBaseType<Actions>>;
|
|
29
33
|
manifest?: AppManifest;
|
|
30
34
|
config?: AppConfig<TConfig>;
|
|
35
|
+
settings?: AppSettings;
|
|
31
36
|
modules?: AppScriptModule;
|
|
32
37
|
instance?: AppModulesInstance<TModules>;
|
|
33
38
|
};
|
package/src/errors.ts
CHANGED
|
@@ -86,6 +86,49 @@ export class AppConfigError extends Error {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Represents an error that occurs while fetching application settings.
|
|
91
|
+
*/
|
|
92
|
+
export class AppSettingsError extends Error {
|
|
93
|
+
/**
|
|
94
|
+
* Creates an instance of `AppSettingsError` based on the HTTP response status.
|
|
95
|
+
* @param response The HTTP response.
|
|
96
|
+
* @param options Additional error options.
|
|
97
|
+
* @returns An instance of `AppSettingsError` based on the HTTP response status.
|
|
98
|
+
*/
|
|
99
|
+
static fromHttpResponse(response: Response, options?: ErrorOptions): AppSettingsError {
|
|
100
|
+
switch (response.status) {
|
|
101
|
+
case 401:
|
|
102
|
+
return new AppSettingsError(
|
|
103
|
+
'unauthorized',
|
|
104
|
+
'failed to load application settings, request not authorized',
|
|
105
|
+
options,
|
|
106
|
+
);
|
|
107
|
+
case 404:
|
|
108
|
+
return new AppSettingsError('not_found', 'application not found', options);
|
|
109
|
+
}
|
|
110
|
+
return new AppSettingsError(
|
|
111
|
+
'unknown',
|
|
112
|
+
`failed to load application settings, status code ${response.status}`,
|
|
113
|
+
options,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Creates an instance of `AppSettingsError`.
|
|
119
|
+
* @param type The type of the application error.
|
|
120
|
+
* @param message The error message.
|
|
121
|
+
* @param options Additional error options.
|
|
122
|
+
*/
|
|
123
|
+
constructor(
|
|
124
|
+
public readonly type: AppErrorType,
|
|
125
|
+
message?: string,
|
|
126
|
+
options?: ErrorOptions,
|
|
127
|
+
) {
|
|
128
|
+
super(message, options);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
89
132
|
/**
|
|
90
133
|
* Represents an error that occurs when loading the application script.
|
|
91
134
|
*/
|
package/src/types.ts
CHANGED
|
@@ -23,6 +23,10 @@ export type AppEnv<TEnv extends ConfigEnvironment = ConfigEnvironment, TProps =
|
|
|
23
23
|
// TODO: change to module-services when new app service is created
|
|
24
24
|
export type ModuleDeps = [HttpModule, ServiceDiscoveryModule, EventModule];
|
|
25
25
|
|
|
26
|
+
export interface AppSettings {
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
// TODO: remove `report` and `launcher` when legacy apps are removed
|
|
27
31
|
export type AppType = 'standalone' | 'report' | 'launcher' | 'template';
|
|
28
32
|
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '6.
|
|
2
|
+
export const version = '6.1.1';
|