@equinor/fusion-framework-module-analytics 3.0.1 → 3.0.3

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/esm/AnalyticsConfigurator.js +11 -2
  3. package/dist/esm/AnalyticsConfigurator.js.map +1 -1
  4. package/dist/esm/AnalyticsProvider.js +12 -2
  5. package/dist/esm/AnalyticsProvider.js.map +1 -1
  6. package/dist/esm/adapters/ConsoleAnalyticsAdapter.js +7 -1
  7. package/dist/esm/adapters/ConsoleAnalyticsAdapter.js.map +1 -1
  8. package/dist/esm/adapters/FusionAnalyticsAdapter.js +6 -1
  9. package/dist/esm/adapters/FusionAnalyticsAdapter.js.map +1 -1
  10. package/dist/esm/collectors/AppLoadedCollector.js +11 -3
  11. package/dist/esm/collectors/AppLoadedCollector.js.map +1 -1
  12. package/dist/esm/collectors/AppSelectedCollector.js +11 -2
  13. package/dist/esm/collectors/AppSelectedCollector.js.map +1 -1
  14. package/dist/esm/collectors/ContextSelectedCollector.js +11 -2
  15. package/dist/esm/collectors/ContextSelectedCollector.js.map +1 -1
  16. package/dist/esm/collectors/{BaseCollector.js → create-schema.js} +4 -1
  17. package/dist/esm/collectors/create-schema.js.map +1 -0
  18. package/dist/esm/collectors/utils/app-key-schema.js +14 -0
  19. package/dist/esm/collectors/utils/app-key-schema.js.map +1 -0
  20. package/dist/esm/collectors/utils/app-schema.js +19 -0
  21. package/dist/esm/collectors/utils/app-schema.js.map +1 -0
  22. package/dist/esm/collectors/utils/context-schema.js +19 -0
  23. package/dist/esm/collectors/utils/context-schema.js.map +1 -0
  24. package/dist/esm/collectors/utils/extract-app-key-metadata.js +12 -0
  25. package/dist/esm/collectors/utils/extract-app-key-metadata.js.map +1 -0
  26. package/dist/esm/collectors/utils/extract-app-metadata.js +17 -0
  27. package/dist/esm/collectors/utils/extract-app-metadata.js.map +1 -0
  28. package/dist/esm/collectors/utils/extract-context-metadata.js +16 -0
  29. package/dist/esm/collectors/utils/extract-context-metadata.js.map +1 -0
  30. package/dist/esm/enable-analytics.js +1 -0
  31. package/dist/esm/enable-analytics.js.map +1 -1
  32. package/dist/esm/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.js +9 -3
  33. package/dist/esm/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.js.map +1 -1
  34. package/dist/esm/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.js +5 -0
  35. package/dist/esm/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.js.map +1 -1
  36. package/dist/esm/version.js +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/dist/types/AnalyticsConfigurator.d.ts +3 -0
  39. package/dist/types/AnalyticsProvider.d.ts +3 -0
  40. package/dist/types/adapters/ConsoleAnalyticsAdapter.d.ts +7 -1
  41. package/dist/types/adapters/FusionAnalyticsAdapter.d.ts +6 -1
  42. package/dist/types/collectors/AppLoadedCollector.d.ts +9 -3
  43. package/dist/types/collectors/AppSelectedCollector.d.ts +8 -2
  44. package/dist/types/collectors/ContextSelectedCollector.d.ts +8 -2
  45. package/dist/types/collectors/{BaseCollector.d.ts → create-schema.d.ts} +2 -0
  46. package/dist/types/collectors/utils/app-key-schema.d.ts +12 -0
  47. package/dist/types/collectors/utils/app-schema.d.ts +18 -0
  48. package/dist/types/collectors/utils/{extractContextMetadata.d.ts → context-schema.d.ts} +0 -8
  49. package/dist/types/collectors/utils/extract-app-key-metadata.d.ts +10 -0
  50. package/dist/types/collectors/utils/extract-app-metadata.d.ts +10 -0
  51. package/dist/types/collectors/utils/extract-context-metadata.d.ts +10 -0
  52. package/dist/types/version.d.ts +1 -1
  53. package/package.json +14 -14
  54. package/src/AnalyticsConfigurator.ts +7 -0
  55. package/src/AnalyticsProvider.ts +12 -2
  56. package/src/adapters/ConsoleAnalyticsAdapter.ts +7 -1
  57. package/src/adapters/FusionAnalyticsAdapter.ts +6 -1
  58. package/src/collectors/AppLoadedCollector.ts +11 -7
  59. package/src/collectors/AppSelectedCollector.ts +11 -6
  60. package/src/collectors/ContextSelectedCollector.ts +11 -6
  61. package/src/collectors/{BaseCollector.ts → create-schema.ts} +3 -0
  62. package/src/collectors/utils/app-key-schema.ts +17 -0
  63. package/src/collectors/utils/app-schema.ts +22 -0
  64. package/src/collectors/utils/{extractContextMetadata.ts → context-schema.ts} +0 -17
  65. package/src/collectors/utils/extract-app-key-metadata.ts +15 -0
  66. package/src/collectors/utils/extract-app-metadata.ts +20 -0
  67. package/src/collectors/utils/extract-context-metadata.ts +19 -0
  68. package/src/enable-analytics.ts +1 -0
  69. package/src/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.ts +9 -1
  70. package/src/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.ts +5 -0
  71. package/src/version.ts +1 -1
  72. package/dist/esm/collectors/BaseCollector.js.map +0 -1
  73. package/dist/esm/collectors/utils/extractAppMetadata.js +0 -58
  74. package/dist/esm/collectors/utils/extractAppMetadata.js.map +0 -1
  75. package/dist/esm/collectors/utils/extractContextMetadata.js +0 -34
  76. package/dist/esm/collectors/utils/extractContextMetadata.js.map +0 -1
  77. package/dist/types/collectors/utils/extractAppMetadata.d.ts +0 -44
  78. package/src/collectors/utils/extractAppMetadata.ts +0 -68
@@ -22,6 +22,9 @@ import type { AnalyticsEvent } from './types.js';
22
22
  */
23
23
  export declare class AnalyticsConfigurator extends BaseConfigBuilder<AnalyticsConfig> implements IAnalyticsConfigurator {
24
24
  #private;
25
+ /**
26
+ * Registers the async resolvers for `collectors` and `adapters` config keys.
27
+ */
25
28
  constructor();
26
29
  /**
27
30
  * Registers an analytics collector factory.
@@ -23,6 +23,9 @@ import { type ObservableInput, Subscription } from 'rxjs';
23
23
  */
24
24
  export declare class AnalyticsProvider extends BaseModuleProvider<AnalyticsConfig> implements IAnalyticsProvider {
25
25
  #private;
26
+ /**
27
+ * @param config - Resolved analytics module configuration, including collectors and adapters.
28
+ */
26
29
  constructor(config: AnalyticsConfig);
27
30
  /**
28
31
  * Initialises all adapters and collectors, then wires collector output into
@@ -17,7 +17,13 @@ import type { AnalyticsEvent } from '../types.js';
17
17
  * ```
18
18
  */
19
19
  export declare class ConsoleAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent> implements IAnalyticsAdapter {
20
- /** Logs the event to the console. */
20
+ /**
21
+ * Logs the event to the console.
22
+ *
23
+ * @param event - The analytics event to log.
24
+ * @returns Resolves once the event has been logged.
25
+ */
21
26
  registerAnalytic(event: T): Promise<void> | void;
27
+ /** No teardown is required for this adapter. */
22
28
  [Symbol.dispose](): void;
23
29
  }
@@ -41,7 +41,12 @@ export declare class FusionAnalyticsAdapter<T extends AnalyticsEvent = Analytics
41
41
  portalId: string;
42
42
  logExporter: OTLPExporterBase<ReadableLogRecord[]>;
43
43
  });
44
- /** Maps an analytics event to an OTLP `LogRecord` and emits it via the logger. */
44
+ /**
45
+ * Maps an analytics event to an OTLP `LogRecord` and emits it via the logger.
46
+ *
47
+ * @param event - The analytics event to map and emit.
48
+ * @returns Resolves once the event has been emitted to the logger.
49
+ */
45
50
  registerAnalytic(event: T): Promise<void> | void;
46
51
  /** Shuts down the log exporter and logger provider, flushing remaining records. */
47
52
  [Symbol.dispose](): void;
@@ -1,6 +1,6 @@
1
- import { BaseCollector } from './BaseCollector.js';
2
- import { type AppItemType } from './utils/extractAppMetadata.js';
3
- import { type ContextItemType } from './utils/extractContextMetadata.js';
1
+ import { BaseCollector } from './create-schema.js';
2
+ import { type AppItemType } from './utils/app-schema.js';
3
+ import { type ContextItemType } from './utils/context-schema.js';
4
4
  import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
5
5
  import type { AppModuleProvider } from '@equinor/fusion-framework-module-app';
6
6
  import type { IEventModuleProvider } from '@equinor/fusion-framework-module-event';
@@ -32,6 +32,12 @@ export declare class AppLoadedCollector extends BaseCollector<AppItemType, {
32
32
  * @param appProvider - Fusion app module provider for fallback manifest data.
33
33
  */
34
34
  constructor(eventProvider: IEventModuleProvider, appProvider: AppModuleProvider);
35
+ /**
36
+ * Builds an observable that emits an analytics event whenever the app-loaded
37
+ * event fires, falling back to `appProvider` for manifest data when needed.
38
+ *
39
+ * @returns An observable input emitting the app-loaded event's value and attributes.
40
+ */
35
41
  _initialize(): ObservableInput<{
36
42
  value: AppItemType;
37
43
  attributes: {
@@ -1,7 +1,7 @@
1
1
  import { type ObservableInput } from 'rxjs';
2
2
  import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
3
- import { BaseCollector } from './BaseCollector.js';
4
- import { type AppKeyType } from './utils/extractAppMetadata.js';
3
+ import { BaseCollector } from './create-schema.js';
4
+ import { type AppKeyType } from './utils/app-key-schema.js';
5
5
  import type { AppModuleProvider } from '@equinor/fusion-framework-module-app';
6
6
  /**
7
7
  * Collector that emits an analytics event whenever the active Fusion application changes.
@@ -26,6 +26,12 @@ export declare class AppSelectedCollector extends BaseCollector<AppKeyType, {
26
26
  * @param appProvider - Fusion app module provider to observe.
27
27
  */
28
28
  constructor(appProvider: AppModuleProvider);
29
+ /**
30
+ * Builds an observable that emits an analytics event each time the selected
31
+ * app changes, including the previous app's key metadata.
32
+ *
33
+ * @returns An observable input emitting the newly selected app's value and attributes.
34
+ */
29
35
  _initialize(): ObservableInput<{
30
36
  value: AppKeyType;
31
37
  attributes: {
@@ -2,8 +2,8 @@ import { type ObservableInput } from 'rxjs';
2
2
  import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
3
3
  import type { IContextProvider } from '@equinor/fusion-framework-module-context';
4
4
  import type { AppModuleProvider } from '@equinor/fusion-framework-module-app';
5
- import { BaseCollector } from './BaseCollector.js';
6
- import { type ContextItemType } from './utils/extractContextMetadata.js';
5
+ import { BaseCollector } from './create-schema.js';
6
+ import { type ContextItemType } from './utils/context-schema.js';
7
7
  /**
8
8
  * Collector that emits an analytics event whenever the active Fusion context changes.
9
9
  *
@@ -31,6 +31,12 @@ export declare class ContextSelectedCollector extends BaseCollector<ContextItemT
31
31
  * @param appProvider - Fusion app module provider for the current app key.
32
32
  */
33
33
  constructor(contextProvider: IContextProvider, appProvider: AppModuleProvider);
34
+ /**
35
+ * Builds an observable that emits an analytics event each time the selected
36
+ * context changes, including the previous context and current app key.
37
+ *
38
+ * @returns An observable input emitting the newly selected context's value and attributes.
39
+ */
34
40
  _initialize(): ObservableInput<{
35
41
  value: ContextItemType;
36
42
  attributes: {
@@ -70,6 +70,8 @@ export declare abstract class BaseCollector<TValue extends AnyValue, TAttr exten
70
70
  * Subscribes to the source returned by {@link BaseCollector._initialize},
71
71
  * validates each emission with the Zod schema, and publishes through the
72
72
  * internal subject.
73
+ *
74
+ * @returns Resolves once the source has been subscribed to.
73
75
  */
74
76
  initialize(): Promise<void> | void;
75
77
  /**
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Zod schema for an object containing an optional `appKey` string.
4
+ *
5
+ * @remarks
6
+ * Used by {@link AppSelectedCollector} to validate the event body.
7
+ */
8
+ export declare const appKeySchema: z.ZodNullable<z.ZodOptional<z.ZodObject<{
9
+ appKey: z.ZodOptional<z.ZodString>;
10
+ }, z.core.$strip>>>;
11
+ /** Inferred type from {@link appKeySchema}. */
12
+ export type AppKeyType = z.infer<typeof appKeySchema>;
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Zod schema for a Fusion application metadata object.
4
+ *
5
+ * @remarks
6
+ * Validates core app fields (appKey, displayName, type) and optional build
7
+ * and category information. Used by {@link AppLoadedCollector}.
8
+ */
9
+ export declare const appSchema: z.ZodOptional<z.ZodObject<{
10
+ appKey: z.ZodString;
11
+ displayName: z.ZodString;
12
+ type: z.ZodString;
13
+ categoryName: z.ZodOptional<z.ZodString>;
14
+ buildVersion: z.ZodOptional<z.ZodString>;
15
+ buildTag: z.ZodNullable<z.ZodOptional<z.ZodString>>;
16
+ }, z.core.$strip>>;
17
+ /** Inferred type from {@link appSchema}. */
18
+ export type AppItemType = z.infer<typeof appSchema>;
@@ -1,5 +1,4 @@
1
1
  import { z } from 'zod';
2
- import type { ContextItem } from '@equinor/fusion-framework-module-context';
3
2
  /**
4
3
  * Zod schema for a Fusion context metadata object.
5
4
  *
@@ -16,10 +15,3 @@ export declare const contextSchema: z.ZodNullable<z.ZodOptional<z.ZodObject<{
16
15
  }, z.core.$strip>>>;
17
16
  /** Inferred type from {@link contextSchema}. */
18
17
  export type ContextItemType = z.infer<typeof contextSchema>;
19
- /**
20
- * Extracts context metadata from a `ContextItem` for analytics events.
21
- *
22
- * @param context - The Fusion context item.
23
- * @returns An object with id, type, and optional title, externalId, and source.
24
- */
25
- export declare const extractContextMetadata: (context: ContextItem) => z.input<typeof contextSchema>;
@@ -0,0 +1,10 @@
1
+ import type { CurrentApp } from '@equinor/fusion-framework-module-app';
2
+ import type { z } from 'zod';
3
+ import type { appKeySchema } from './app-key-schema.js';
4
+ /**
5
+ * Extracts app-key metadata from a `CurrentApp` instance.
6
+ *
7
+ * @param app - The current app object.
8
+ * @returns An object containing the optional `appKey`.
9
+ */
10
+ export declare const extractAppKeyMetadata: (app: CurrentApp) => z.input<typeof appKeySchema>;
@@ -0,0 +1,10 @@
1
+ import type { AppManifest } from '@equinor/fusion-framework-module-app';
2
+ import type { z } from 'zod';
3
+ import type { appSchema } from './app-schema.js';
4
+ /**
5
+ * Extracts detailed app metadata from an `AppManifest` for analytics events.
6
+ *
7
+ * @param app - The application manifest.
8
+ * @returns An object with appKey, displayName, type, and optional build/category info.
9
+ */
10
+ export declare const extractAppMetadata: (app: AppManifest) => z.input<typeof appSchema>;
@@ -0,0 +1,10 @@
1
+ import type { ContextItem } from '@equinor/fusion-framework-module-context';
2
+ import type { z } from 'zod';
3
+ import type { contextSchema } from './context-schema.js';
4
+ /**
5
+ * Extracts context metadata from a `ContextItem` for analytics events.
6
+ *
7
+ * @param context - The Fusion context item.
8
+ * @returns An object with id, type, and optional title, externalId, and source.
9
+ */
10
+ export declare const extractContextMetadata: (context: ContextItem) => z.input<typeof contextSchema>;
@@ -1 +1 @@
1
- export declare const version = "3.0.1";
1
+ export declare const version = "3.0.3";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-module-analytics",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "Fusion module for collecting and exporting application analytics using OpenTelemetry standards",
5
5
  "main": "dist/esm/index.js",
6
6
  "type": "module",
@@ -39,29 +39,29 @@
39
39
  "directory": "packages/modules/analytics"
40
40
  },
41
41
  "dependencies": {
42
- "@opentelemetry/api-logs": "^0.220.0",
43
- "@opentelemetry/exporter-logs-otlp-http": "^0.220.0",
44
- "@opentelemetry/otlp-exporter-base": "^0.220.0",
45
- "@opentelemetry/otlp-transformer": "^0.220.0",
42
+ "@opentelemetry/api-logs": "^0.221.0",
43
+ "@opentelemetry/exporter-logs-otlp-http": "^0.221.0",
44
+ "@opentelemetry/otlp-exporter-base": "^0.221.0",
45
+ "@opentelemetry/otlp-transformer": "^0.221.0",
46
46
  "@opentelemetry/resources": "^2.2.0",
47
- "@opentelemetry/sdk-logs": "^0.220.0",
47
+ "@opentelemetry/sdk-logs": "^0.221.0",
48
48
  "deepmerge": "^4.3.1",
49
49
  "rxjs": "^7.8.1",
50
50
  "uuid": "^14.0.0",
51
51
  "zod": "^4.4.3",
52
- "@equinor/fusion-framework-module": "6.1.0",
53
- "@equinor/fusion-framework-module-app": "8.0.2",
54
- "@equinor/fusion-framework-module-context": "8.0.0",
55
- "@equinor/fusion-framework-module-event": "6.0.0",
56
- "@equinor/fusion-framework-module-http": "8.0.3"
52
+ "@equinor/fusion-framework-module-context": "8.0.1",
53
+ "@equinor/fusion-framework-module-app": "8.0.3",
54
+ "@equinor/fusion-framework-module": "6.1.1",
55
+ "@equinor/fusion-framework-module-event": "6.0.1",
56
+ "@equinor/fusion-framework-module-http": "8.0.4"
57
57
  },
58
58
  "devDependencies": {
59
- "typescript": "^6.0.3",
59
+ "typescript": "^7.0.2",
60
60
  "vitest": "^4.1.0",
61
- "@equinor/fusion-observable": "^9.1.0"
61
+ "@equinor/fusion-observable": "^9.1.1"
62
62
  },
63
63
  "peerDependencies": {
64
- "@equinor/fusion-observable": "9.1.0"
64
+ "@equinor/fusion-observable": "9.1.1"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsc -b",
@@ -35,6 +35,9 @@ export class AnalyticsConfigurator
35
35
  #collectorCallbacks: Record<string, ConfigBuilderCallback<IAnalyticsCollector>> = {};
36
36
  #adapterCallbacks: Record<string, ConfigBuilderCallback<IAnalyticsAdapter>> = {};
37
37
 
38
+ /**
39
+ * Registers the async resolvers for `collectors` and `adapters` config keys.
40
+ */
38
41
  constructor() {
39
42
  super();
40
43
 
@@ -42,8 +45,10 @@ export class AnalyticsConfigurator
42
45
  this._set(
43
46
  'collectors',
44
47
  (args: ConfigBuilderCallbackArgs): ObservableInput<Record<string, IAnalyticsCollector>> => {
48
+ // Resolve each collector factory and drop any that resolve to a falsy value
45
49
  return from(Object.entries(this.#collectorCallbacks)).pipe(
46
50
  mergeMap(([identifier, collectorFn]) =>
51
+ // Resolve the collector's own Promise/Observable factory result
47
52
  from(collectorFn(args)).pipe(
48
53
  filter((collector): collector is IAnalyticsCollector => !!collector),
49
54
  map((collector) => [identifier, collector] as const),
@@ -66,8 +71,10 @@ export class AnalyticsConfigurator
66
71
  this._set(
67
72
  'adapters',
68
73
  (args: ConfigBuilderCallbackArgs): ObservableInput<Record<string, IAnalyticsAdapter>> => {
74
+ // Resolve each adapter factory and drop any that resolve to a falsy value
69
75
  return from(Object.entries(this.#adapterCallbacks)).pipe(
70
76
  mergeMap(([identifier, adapterFn]) =>
77
+ // Resolve the adapter's own Promise/Observable factory result
71
78
  from(adapterFn(args)).pipe(
72
79
  filter((adapter): adapter is IAnalyticsAdapter => !!adapter),
73
80
  map((adapter) => [identifier, adapter] as const),
@@ -17,6 +17,9 @@ import type { IAnalyticsAdapter } from './adapters/AnalyticsAdapter.interface.js
17
17
  * with either `subscription.unsubscribe()` or `using` / `Symbol.dispose`.
18
18
  */
19
19
  class DisposableSubscription extends Subscription {
20
+ /**
21
+ * @param subscription - The underlying subscription to wrap with `Symbol.dispose` support.
22
+ */
20
23
  constructor(subscription: Subscription) {
21
24
  super(subscription.unsubscribe);
22
25
  }
@@ -52,6 +55,9 @@ export class AnalyticsProvider
52
55
  #collectors: Record<string, IAnalyticsCollector>;
53
56
  #adapters: Record<string, IAnalyticsAdapter>;
54
57
 
58
+ /**
59
+ * @param config - Resolved analytics module configuration, including collectors and adapters.
60
+ */
55
61
  constructor(config: AnalyticsConfig) {
56
62
  super({ version, config });
57
63
 
@@ -78,9 +84,11 @@ export class AnalyticsProvider
78
84
  * @returns A promise that resolves when all adapters and collectors are initialised.
79
85
  */
80
86
  async initialize(): Promise<void> {
87
+ // Kick off initialization for every collector, tolerating individual failures
81
88
  const initializedCollectors = Object.values(this.#collectors).map((collector) =>
82
89
  Promise.resolve(collector.initialize?.()),
83
90
  );
91
+ // Kick off initialization for every adapter, tolerating individual failures
84
92
  const initializedAdapters = Object.values(this.#adapters).map((adapters) =>
85
93
  Promise.resolve(adapters.initialize?.()),
86
94
  );
@@ -88,6 +96,7 @@ export class AnalyticsProvider
88
96
  await Promise.allSettled(initializedCollectors);
89
97
  await Promise.allSettled(initializedAdapters);
90
98
 
99
+ // Forward every collector's emissions into the shared analytics subject
91
100
  for (const collector of Object.values(this.#collectors)) {
92
101
  const subscription = collector.subscribe({
93
102
  next: (event) => {
@@ -100,6 +109,7 @@ export class AnalyticsProvider
100
109
 
101
110
  const adapterSubscription = this.#analytics.subscribe({
102
111
  next: (event) => {
112
+ // Dispatch every emitted event to all registered adapters
103
113
  for (const adapter of Object.values(this.#adapters)) {
104
114
  adapter.registerAnalytic(event);
105
115
  }
@@ -114,7 +124,7 @@ export class AnalyticsProvider
114
124
  * @param event - The analytics event to track.
115
125
  */
116
126
  trackAnalytic(event: AnalyticsEvent): void {
117
- // @TODO: Validate AnalyticsEvent includes name, value and attributes
127
+ // TODO(#5098): Validate AnalyticsEvent includes name, value and attributes
118
128
  this.#analytics.next(event);
119
129
  }
120
130
 
@@ -127,7 +137,7 @@ export class AnalyticsProvider
127
137
  */
128
138
  trackAnalytic$(analytic$: ObservableInput<AnalyticsEvent>): Disposable & Subscription {
129
139
  const subscription = from(analytic$)
130
- // @TODO: Validate AnalyticsEvent includes name, value and attributes
140
+ // TODO(#5098): Validate AnalyticsEvent includes name, value and attributes
131
141
  .subscribe({
132
142
  next: (event) => {
133
143
  this.#analytics.next(event);
@@ -20,11 +20,17 @@ import type { AnalyticsEvent } from '../types.js';
20
20
  export class ConsoleAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent>
21
21
  implements IAnalyticsAdapter
22
22
  {
23
- /** Logs the event to the console. */
23
+ /**
24
+ * Logs the event to the console.
25
+ *
26
+ * @param event - The analytics event to log.
27
+ * @returns Resolves once the event has been logged.
28
+ */
24
29
  registerAnalytic(event: T): Promise<void> | void {
25
30
  console.log('Analytics::Adapter::Console', event);
26
31
  }
27
32
 
33
+ /** No teardown is required for this adapter. */
28
34
  [Symbol.dispose]() {
29
35
  // no-op
30
36
  }
@@ -71,7 +71,12 @@ export class FusionAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent>
71
71
  this.#logger = this.#loggerProvider.getLogger('fusion');
72
72
  }
73
73
 
74
- /** Maps an analytics event to an OTLP `LogRecord` and emits it via the logger. */
74
+ /**
75
+ * Maps an analytics event to an OTLP `LogRecord` and emits it via the logger.
76
+ *
77
+ * @param event - The analytics event to map and emit.
78
+ * @returns Resolves once the event has been emitted to the logger.
79
+ */
75
80
  registerAnalytic(event: T): Promise<void> | void {
76
81
  const logRecord: Partial<LogRecord> = {
77
82
  eventName: event.name,
@@ -1,10 +1,8 @@
1
- import { BaseCollector, createSchema } from './BaseCollector.js';
2
- import { type AppItemType, appSchema, extractAppMetadata } from './utils/extractAppMetadata.js';
3
- import {
4
- type ContextItemType,
5
- contextSchema,
6
- extractContextMetadata,
7
- } from './utils/extractContextMetadata.js';
1
+ import { BaseCollector, createSchema } from './create-schema.js';
2
+ import { type AppItemType, appSchema } from './utils/app-schema.js';
3
+ import { extractAppMetadata } from './utils/extract-app-metadata.js';
4
+ import { type ContextItemType, contextSchema } from './utils/context-schema.js';
5
+ import { extractContextMetadata } from './utils/extract-context-metadata.js';
8
6
  import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
9
7
 
10
8
  import type {
@@ -58,6 +56,12 @@ export class AppLoadedCollector
58
56
  this.#appProvider = appProvider;
59
57
  }
60
58
 
59
+ /**
60
+ * Builds an observable that emits an analytics event whenever the app-loaded
61
+ * event fires, falling back to `appProvider` for manifest data when needed.
62
+ *
63
+ * @returns An observable input emitting the app-loaded event's value and attributes.
64
+ */
61
65
  _initialize(): ObservableInput<{
62
66
  value: AppItemType;
63
67
  attributes: { context?: ContextItemType };
@@ -1,11 +1,8 @@
1
1
  import { map, type ObservableInput, pairwise } from 'rxjs';
2
2
  import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
3
- import { BaseCollector, createSchema } from './BaseCollector.js';
4
- import {
5
- type AppKeyType,
6
- appKeySchema,
7
- extractAppKeyMetadata,
8
- } from './utils/extractAppMetadata.js';
3
+ import { BaseCollector, createSchema } from './create-schema.js';
4
+ import { type AppKeyType, appKeySchema } from './utils/app-key-schema.js';
5
+ import { extractAppKeyMetadata } from './utils/extract-app-key-metadata.js';
9
6
 
10
7
  import type { AppModuleProvider } from '@equinor/fusion-framework-module-app';
11
8
 
@@ -43,12 +40,20 @@ export class AppSelectedCollector
43
40
  this.#appProvider = appProvider;
44
41
  }
45
42
 
43
+ /**
44
+ * Builds an observable that emits an analytics event each time the selected
45
+ * app changes, including the previous app's key metadata.
46
+ *
47
+ * @returns An observable input emitting the newly selected app's value and attributes.
48
+ */
46
49
  _initialize(): ObservableInput<{
47
50
  value: AppKeyType;
48
51
  attributes: { previous?: AppKeyType };
49
52
  }> {
53
+ // Pair each app-selection emission with the previously selected app
50
54
  const appSelected$ = this.#appProvider.current$.pipe(pairwise());
51
55
 
56
+ // Map the [previous, next] pair into the analytics event shape
52
57
  const data$ = appSelected$.pipe(
53
58
  map(([prev, next]) => {
54
59
  return {
@@ -3,12 +3,9 @@ import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
3
3
  import type { IContextProvider } from '@equinor/fusion-framework-module-context';
4
4
  import type { AppModuleProvider } from '@equinor/fusion-framework-module-app';
5
5
  import { z } from 'zod';
6
- import { BaseCollector, createSchema } from './BaseCollector.js';
7
- import {
8
- type ContextItemType,
9
- contextSchema,
10
- extractContextMetadata,
11
- } from './utils/extractContextMetadata.js';
6
+ import { BaseCollector, createSchema } from './create-schema.js';
7
+ import { type ContextItemType, contextSchema } from './utils/context-schema.js';
8
+ import { extractContextMetadata } from './utils/extract-context-metadata.js';
12
9
 
13
10
  /** Zod schema for the `context-selected` event (value + attributes). */
14
11
  const eventSchema = createSchema(
@@ -50,10 +47,17 @@ export class ContextSelectedCollector
50
47
  this.#appProvider = appProvider;
51
48
  }
52
49
 
50
+ /**
51
+ * Builds an observable that emits an analytics event each time the selected
52
+ * context changes, including the previous context and current app key.
53
+ *
54
+ * @returns An observable input emitting the newly selected context's value and attributes.
55
+ */
53
56
  _initialize(): ObservableInput<{
54
57
  value: ContextItemType;
55
58
  attributes: { previous?: ContextItemType; appKey?: string };
56
59
  }> {
60
+ // Track context changes, pairing each new context with the previously selected one
57
61
  const contextSelected$ = this.#contextProvider.currentContext$.pipe(
58
62
  // Only emit when an actual change has happened.
59
63
  distinctUntilChanged((prev, curr) => prev?.id === curr?.id),
@@ -61,6 +65,7 @@ export class ContextSelectedCollector
61
65
  pairwise(),
62
66
  );
63
67
 
68
+ // Map the [previous, next] pair into the analytics event shape
64
69
  const data$ = contextSelected$.pipe(
65
70
  map(([prev, next]) => {
66
71
  return {
@@ -92,8 +92,11 @@ export abstract class BaseCollector<
92
92
  * Subscribes to the source returned by {@link BaseCollector._initialize},
93
93
  * validates each emission with the Zod schema, and publishes through the
94
94
  * internal subject.
95
+ *
96
+ * @returns Resolves once the source has been subscribed to.
95
97
  */
96
98
  initialize(): Promise<void> | void {
99
+ // Shape each emission into the named/validated event before publishing
97
100
  from(this._initialize())
98
101
  .pipe(
99
102
  map(({ value, attributes }) => {
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Zod schema for an object containing an optional `appKey` string.
5
+ *
6
+ * @remarks
7
+ * Used by {@link AppSelectedCollector} to validate the event body.
8
+ */
9
+ export const appKeySchema = z
10
+ .object({
11
+ appKey: z.string().optional(),
12
+ })
13
+ .optional()
14
+ .nullable();
15
+
16
+ /** Inferred type from {@link appKeySchema}. */
17
+ export type AppKeyType = z.infer<typeof appKeySchema>;
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Zod schema for a Fusion application metadata object.
5
+ *
6
+ * @remarks
7
+ * Validates core app fields (appKey, displayName, type) and optional build
8
+ * and category information. Used by {@link AppLoadedCollector}.
9
+ */
10
+ export const appSchema = z
11
+ .object({
12
+ appKey: z.string(),
13
+ displayName: z.string(),
14
+ type: z.string(),
15
+ categoryName: z.string().optional(),
16
+ buildVersion: z.string().optional(),
17
+ buildTag: z.string().optional().nullable(),
18
+ })
19
+ .optional();
20
+
21
+ /** Inferred type from {@link appSchema}. */
22
+ export type AppItemType = z.infer<typeof appSchema>;
@@ -1,5 +1,4 @@
1
1
  import { z } from 'zod';
2
- import type { ContextItem } from '@equinor/fusion-framework-module-context';
3
2
 
4
3
  /**
5
4
  * Zod schema for a Fusion context metadata object.
@@ -21,19 +20,3 @@ export const contextSchema = z
21
20
 
22
21
  /** Inferred type from {@link contextSchema}. */
23
22
  export type ContextItemType = z.infer<typeof contextSchema>;
24
-
25
- /**
26
- * Extracts context metadata from a `ContextItem` for analytics events.
27
- *
28
- * @param context - The Fusion context item.
29
- * @returns An object with id, type, and optional title, externalId, and source.
30
- */
31
- export const extractContextMetadata = (context: ContextItem): z.input<typeof contextSchema> => {
32
- return {
33
- id: context.id,
34
- externalId: context.externalId ?? undefined,
35
- title: context.title ?? undefined,
36
- type: context.type.id,
37
- source: context.source ?? undefined,
38
- };
39
- };
@@ -0,0 +1,15 @@
1
+ import type { CurrentApp } from '@equinor/fusion-framework-module-app';
2
+ import type { z } from 'zod';
3
+ import type { appKeySchema } from './app-key-schema.js';
4
+
5
+ /**
6
+ * Extracts app-key metadata from a `CurrentApp` instance.
7
+ *
8
+ * @param app - The current app object.
9
+ * @returns An object containing the optional `appKey`.
10
+ */
11
+ export const extractAppKeyMetadata = (app: CurrentApp): z.input<typeof appKeySchema> => {
12
+ return {
13
+ appKey: app?.appKey,
14
+ };
15
+ };
@@ -0,0 +1,20 @@
1
+ import type { AppManifest } from '@equinor/fusion-framework-module-app';
2
+ import type { z } from 'zod';
3
+ import type { appSchema } from './app-schema.js';
4
+
5
+ /**
6
+ * Extracts detailed app metadata from an `AppManifest` for analytics events.
7
+ *
8
+ * @param app - The application manifest.
9
+ * @returns An object with appKey, displayName, type, and optional build/category info.
10
+ */
11
+ export const extractAppMetadata = (app: AppManifest): z.input<typeof appSchema> => {
12
+ return {
13
+ appKey: app.appKey,
14
+ displayName: app.displayName,
15
+ type: app.type,
16
+ categoryName: app.category?.name,
17
+ buildVersion: app.build?.version,
18
+ buildTag: app.build?.tag,
19
+ };
20
+ };