@equinor/fusion-framework-module-analytics 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/README.md +147 -130
- package/dist/esm/AnalyticsConfigurator.js +22 -17
- package/dist/esm/AnalyticsConfigurator.js.map +1 -1
- package/dist/esm/AnalyticsProvider.js +39 -18
- package/dist/esm/AnalyticsProvider.js.map +1 -1
- package/dist/esm/adapters/ConsoleAnalyticsAdapter.js +15 -1
- package/dist/esm/adapters/ConsoleAnalyticsAdapter.js.map +1 -1
- package/dist/esm/adapters/FusionAnalyticsAdapter.js +32 -3
- package/dist/esm/adapters/FusionAnalyticsAdapter.js.map +1 -1
- package/dist/esm/adapters/index.js.map +1 -1
- package/dist/esm/collectors/AppLoadedCollector.js +21 -5
- package/dist/esm/collectors/AppLoadedCollector.js.map +1 -1
- package/dist/esm/collectors/AppSelectedCollector.js +17 -5
- package/dist/esm/collectors/AppSelectedCollector.js.map +1 -1
- package/dist/esm/collectors/BaseCollector.js +48 -3
- package/dist/esm/collectors/BaseCollector.js.map +1 -1
- package/dist/esm/collectors/ContextSelectedCollector.js +20 -5
- package/dist/esm/collectors/ContextSelectedCollector.js.map +1 -1
- package/dist/esm/collectors/index.js.map +1 -1
- package/dist/esm/collectors/utils/extractAppMetadata.js +25 -4
- package/dist/esm/collectors/utils/extractAppMetadata.js.map +1 -1
- package/dist/esm/collectors/utils/extractContextMetadata.js +13 -2
- package/dist/esm/collectors/utils/extractContextMetadata.js.map +1 -1
- package/dist/esm/enable-analytics.js +18 -3
- package/dist/esm/enable-analytics.js.map +1 -1
- package/dist/esm/index.js +18 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.js +26 -4
- package/dist/esm/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.js.map +1 -1
- package/dist/esm/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.js +36 -7
- package/dist/esm/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.js.map +1 -1
- package/dist/esm/logExporters/index.js +8 -0
- package/dist/esm/logExporters/index.js.map +1 -1
- package/dist/esm/module.js +8 -8
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/AnalyticsConfigurator.d.ts +22 -17
- package/dist/types/AnalyticsConfigurator.interface.d.ts +41 -12
- package/dist/types/AnalyticsProvider.d.ts +32 -18
- package/dist/types/AnalyticsProvider.interface.d.ts +28 -6
- package/dist/types/adapters/AnalyticsAdapter.interface.d.ts +15 -2
- package/dist/types/adapters/ConsoleAnalyticsAdapter.d.ts +15 -1
- package/dist/types/adapters/FusionAnalyticsAdapter.d.ts +32 -3
- package/dist/types/adapters/index.d.ts +8 -0
- package/dist/types/collectors/AnalyticsCollector.interface.d.ts +11 -2
- package/dist/types/collectors/AppLoadedCollector.d.ts +20 -2
- package/dist/types/collectors/AppSelectedCollector.d.ts +16 -2
- package/dist/types/collectors/BaseCollector.d.ts +58 -3
- package/dist/types/collectors/ContextSelectedCollector.d.ts +19 -2
- package/dist/types/collectors/index.d.ts +8 -0
- package/dist/types/collectors/utils/extractAppMetadata.d.ts +27 -0
- package/dist/types/collectors/utils/extractContextMetadata.d.ts +14 -0
- package/dist/types/enable-analytics.d.ts +19 -3
- package/dist/types/index.d.ts +18 -0
- package/dist/types/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.d.ts +21 -3
- package/dist/types/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.d.ts +24 -1
- package/dist/types/logExporters/index.d.ts +8 -0
- package/dist/types/module.d.ts +15 -15
- package/dist/types/types.d.ts +38 -7
- package/dist/types/version.d.ts +1 -1
- package/package.json +11 -11
- package/src/AnalyticsConfigurator.interface.ts +41 -12
- package/src/AnalyticsConfigurator.ts +22 -17
- package/src/AnalyticsProvider.interface.ts +28 -6
- package/src/AnalyticsProvider.ts +39 -18
- package/src/adapters/AnalyticsAdapter.interface.ts +15 -2
- package/src/adapters/ConsoleAnalyticsAdapter.ts +15 -1
- package/src/adapters/FusionAnalyticsAdapter.ts +32 -3
- package/src/adapters/index.ts +8 -0
- package/src/collectors/AnalyticsCollector.interface.ts +11 -2
- package/src/collectors/AppLoadedCollector.ts +21 -5
- package/src/collectors/AppSelectedCollector.ts +17 -5
- package/src/collectors/BaseCollector.ts +58 -3
- package/src/collectors/ContextSelectedCollector.ts +20 -5
- package/src/collectors/index.ts +8 -0
- package/src/collectors/utils/extractAppMetadata.ts +27 -4
- package/src/collectors/utils/extractContextMetadata.ts +14 -2
- package/src/enable-analytics.ts +19 -3
- package/src/index.ts +19 -0
- package/src/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.ts +26 -4
- package/src/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.ts +36 -7
- package/src/logExporters/index.ts +8 -0
- package/src/module.ts +15 -15
- package/src/types.ts +39 -8
- package/src/version.ts +1 -1
|
@@ -2,11 +2,25 @@ import type { IAnalyticsAdapter } from './AnalyticsAdapter.interface.js';
|
|
|
2
2
|
import type { AnalyticsEvent } from '../types.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Analytics adapter that logs every event to the browser console.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Useful during development and debugging. Each event is logged with the
|
|
9
|
+
* prefix `Analytics::Adapter::Console`.
|
|
10
|
+
*
|
|
11
|
+
* @template T - Analytics event type, defaults to {@link AnalyticsEvent}.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { ConsoleAnalyticsAdapter } from '@equinor/fusion-framework-module-analytics/adapters';
|
|
16
|
+
*
|
|
17
|
+
* builder.setAdapter('console', async () => new ConsoleAnalyticsAdapter());
|
|
18
|
+
* ```
|
|
6
19
|
*/
|
|
7
20
|
export class ConsoleAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent>
|
|
8
21
|
implements IAnalyticsAdapter
|
|
9
22
|
{
|
|
23
|
+
/** Logs the event to the console. */
|
|
10
24
|
registerAnalytic(event: T): Promise<void> | void {
|
|
11
25
|
console.log('Analytics::Adapter::Console', event);
|
|
12
26
|
}
|
|
@@ -18,10 +18,30 @@ import { version } from '../version.js';
|
|
|
18
18
|
import { v7 as uuid } from 'uuid';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* Analytics adapter that forwards events to an OpenTelemetry log exporter.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* Uses `@opentelemetry/sdk-logs` to batch and export log records through the
|
|
25
|
+
* provided {@link OTLPExporterBase} transport. Each analytics event is mapped
|
|
26
|
+
* to an OTLP `LogRecord` with severity `INFO`.
|
|
27
|
+
*
|
|
28
|
+
* Resource attributes automatically include the module version, a unique
|
|
29
|
+
* session ID (UUIDv7), and the portal ID supplied at construction time.
|
|
30
|
+
*
|
|
31
|
+
* @template T - Analytics event type, defaults to {@link AnalyticsEvent}.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { FusionAnalyticsAdapter } from '@equinor/fusion-framework-module-analytics/adapters';
|
|
36
|
+
* import { OTLPLogExporter } from '@equinor/fusion-framework-module-analytics/logExporters';
|
|
37
|
+
*
|
|
38
|
+
* builder.setAdapter('fusion-log', async () => {
|
|
39
|
+
* const logExporter = new OTLPLogExporter({ url: '/v1/logs' });
|
|
40
|
+
* return new FusionAnalyticsAdapter({ portalId: 'my-portal', logExporter });
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @see {@link OTLPExporterBase}
|
|
25
45
|
*/
|
|
26
46
|
export class FusionAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent>
|
|
27
47
|
implements IAnalyticsAdapter
|
|
@@ -30,6 +50,13 @@ export class FusionAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent>
|
|
|
30
50
|
#loggerProvider: LoggerProvider;
|
|
31
51
|
#logger: Logger;
|
|
32
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Creates a new `FusionAnalyticsAdapter`.
|
|
55
|
+
*
|
|
56
|
+
* @param args - Construction options.
|
|
57
|
+
* @param args.portalId - Portal identifier attached to every exported log record.
|
|
58
|
+
* @param args.logExporter - An OTLP-compatible log exporter for transport.
|
|
59
|
+
*/
|
|
33
60
|
constructor(args: { portalId: string; logExporter: OTLPExporterBase<ReadableLogRecord[]> }) {
|
|
34
61
|
this.#logExporter = args.logExporter;
|
|
35
62
|
|
|
@@ -44,6 +71,7 @@ export class FusionAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent>
|
|
|
44
71
|
this.#logger = this.#loggerProvider.getLogger('fusion');
|
|
45
72
|
}
|
|
46
73
|
|
|
74
|
+
/** Maps an analytics event to an OTLP `LogRecord` and emits it via the logger. */
|
|
47
75
|
registerAnalytic(event: T): Promise<void> | void {
|
|
48
76
|
const logRecord: Partial<LogRecord> = {
|
|
49
77
|
eventName: event.name,
|
|
@@ -54,6 +82,7 @@ export class FusionAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent>
|
|
|
54
82
|
this.#logger.emit(logRecord);
|
|
55
83
|
}
|
|
56
84
|
|
|
85
|
+
/** Shuts down the log exporter and logger provider, flushing remaining records. */
|
|
57
86
|
[Symbol.dispose]() {
|
|
58
87
|
this.#logExporter.shutdown();
|
|
59
88
|
this.#loggerProvider.shutdown();
|
package/src/adapters/index.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports for built-in analytics adapters.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Import from `'@equinor/fusion-framework-module-analytics/adapters'`.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
1
9
|
export { IAnalyticsAdapter } from './AnalyticsAdapter.interface.js';
|
|
2
10
|
export { ConsoleAnalyticsAdapter } from './ConsoleAnalyticsAdapter.js';
|
|
3
11
|
export { FusionAnalyticsAdapter } from './FusionAnalyticsAdapter.js';
|
|
@@ -2,9 +2,18 @@ import type { Subscribable } from 'rxjs';
|
|
|
2
2
|
import type { AnalyticsEvent } from '../types.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Contract for an analytics collector that observes application state and emits
|
|
6
|
+
* structured analytics events.
|
|
6
7
|
*
|
|
7
|
-
* @
|
|
8
|
+
* @remarks
|
|
9
|
+
* Implement this interface (or extend {@link BaseCollector}) to create a custom
|
|
10
|
+
* collector. Register it via {@link IAnalyticsConfigurator.setCollector}.
|
|
11
|
+
*
|
|
12
|
+
* The collector must be `Subscribable` so the provider can listen for emitted
|
|
13
|
+
* events. Optionally implement `initialize` for async setup (e.g. resolving
|
|
14
|
+
* module dependencies).
|
|
15
|
+
*
|
|
16
|
+
* @template T - Analytics event type emitted by this collector, defaults to {@link AnalyticsEvent}.
|
|
8
17
|
*/
|
|
9
18
|
export interface IAnalyticsCollector<T extends AnalyticsEvent = AnalyticsEvent>
|
|
10
19
|
extends Subscribable<T> {
|
|
@@ -20,14 +20,26 @@ import type { ContextModule } from '@equinor/fusion-framework-module-context';
|
|
|
20
20
|
|
|
21
21
|
const EVENT_NAME = 'onAppModulesLoaded';
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
* The schema of the data to be sent.
|
|
25
|
-
*/
|
|
23
|
+
/** Zod schema for the `app-loaded` event (value + attributes). */
|
|
26
24
|
const eventSchema = createSchema(appSchema, z.object({ context: contextSchema }));
|
|
27
25
|
|
|
28
26
|
/**
|
|
29
|
-
* Collector
|
|
30
|
-
*
|
|
27
|
+
* Collector that emits an analytics event whenever an application’s modules
|
|
28
|
+
* finish loading.
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* Listens to the framework `onAppModulesLoaded` event, extracts application
|
|
32
|
+
* manifest metadata, and includes the current context (if available) in the
|
|
33
|
+
* event attributes.
|
|
34
|
+
*
|
|
35
|
+
* Register via:
|
|
36
|
+
* ```ts
|
|
37
|
+
* builder.setCollector('app-loaded', async (args) => {
|
|
38
|
+
* const event = await args.requireInstance('event');
|
|
39
|
+
* const app = await args.requireInstance('app');
|
|
40
|
+
* return new AppLoadedCollector(event, app);
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
31
43
|
*/
|
|
32
44
|
export class AppLoadedCollector
|
|
33
45
|
extends BaseCollector<AppItemType, { context?: ContextItemType }>
|
|
@@ -36,6 +48,10 @@ export class AppLoadedCollector
|
|
|
36
48
|
#eventProvider: IEventModuleProvider;
|
|
37
49
|
#appProvider: AppModuleProvider;
|
|
38
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @param eventProvider - Fusion event module provider to listen for app-loaded events.
|
|
53
|
+
* @param appProvider - Fusion app module provider for fallback manifest data.
|
|
54
|
+
*/
|
|
39
55
|
constructor(eventProvider: IEventModuleProvider, appProvider: AppModuleProvider) {
|
|
40
56
|
super('app-loaded', eventSchema);
|
|
41
57
|
this.#eventProvider = eventProvider;
|
|
@@ -11,14 +11,23 @@ import type { AppModuleProvider } from '@equinor/fusion-framework-module-app';
|
|
|
11
11
|
|
|
12
12
|
import { z } from 'zod';
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* The schema of the data to be sent.
|
|
16
|
-
*/
|
|
14
|
+
/** Zod schema for the `app-selected` event (value + attributes). */
|
|
17
15
|
const eventSchema = createSchema(appKeySchema, z.object({ previous: appKeySchema }));
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
|
-
* Collector
|
|
21
|
-
*
|
|
18
|
+
* Collector that emits an analytics event whenever the active Fusion application changes.
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* Listens to `AppModuleProvider.current$`, pairs consecutive values, and emits
|
|
22
|
+
* both the new and previous app key metadata.
|
|
23
|
+
*
|
|
24
|
+
* Register via:
|
|
25
|
+
* ```ts
|
|
26
|
+
* builder.setCollector('app-selected', async (args) => {
|
|
27
|
+
* const app = await args.requireInstance('app');
|
|
28
|
+
* return new AppSelectedCollector(app);
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
22
31
|
*/
|
|
23
32
|
export class AppSelectedCollector
|
|
24
33
|
extends BaseCollector<AppKeyType, { previous?: AppKeyType }>
|
|
@@ -26,6 +35,9 @@ export class AppSelectedCollector
|
|
|
26
35
|
{
|
|
27
36
|
#appProvider: AppModuleProvider;
|
|
28
37
|
|
|
38
|
+
/**
|
|
39
|
+
* @param appProvider - Fusion app module provider to observe.
|
|
40
|
+
*/
|
|
29
41
|
constructor(appProvider: AppModuleProvider) {
|
|
30
42
|
super('app-selected', eventSchema);
|
|
31
43
|
this.#appProvider = appProvider;
|
|
@@ -5,7 +5,14 @@ import { from, map, type ObservableInput, Subject, type Observer, type Unsubscri
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Creates a Zod schema for validating {@link AnalyticsEvent} instances with
|
|
9
|
+
* typed value and attributes.
|
|
10
|
+
*
|
|
11
|
+
* @template TValue - Schema type for the event body.
|
|
12
|
+
* @template TAttr - Schema type for the event attributes.
|
|
13
|
+
* @param value - Zod schema validating the event body.
|
|
14
|
+
* @param attributes - Zod schema validating the event attributes.
|
|
15
|
+
* @returns A Zod object schema matching `{ name, value, attributes? }`.
|
|
9
16
|
*/
|
|
10
17
|
export const createSchema = <TValue = AnyValue, TAttr = AnyValueMap>(
|
|
11
18
|
value: z.ZodSchema<TValue>,
|
|
@@ -18,13 +25,35 @@ export const createSchema = <TValue = AnyValue, TAttr = AnyValueMap>(
|
|
|
18
25
|
});
|
|
19
26
|
};
|
|
20
27
|
|
|
28
|
+
/** Inferred Zod schema type produced by {@link createSchema}. */
|
|
21
29
|
export type CollectorSchema<TValue = AnyValue, TAttr = AnyValueMap> = ReturnType<
|
|
22
30
|
typeof createSchema<TValue, TAttr>
|
|
23
31
|
>;
|
|
24
32
|
|
|
25
33
|
/**
|
|
26
|
-
*
|
|
27
|
-
* emitting.
|
|
34
|
+
* Abstract base class for analytics collectors that validates events against a
|
|
35
|
+
* Zod schema before emitting.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* Subclasses implement {@link BaseCollector._initialize | _initialize} to return
|
|
39
|
+
* an observable of raw `{ value, attributes }` objects. `BaseCollector` wraps
|
|
40
|
+
* each emission with the collector name, validates it against the supplied schema,
|
|
41
|
+
* and publishes through an internal `Subject`.
|
|
42
|
+
*
|
|
43
|
+
* @template TValue - The event body type.
|
|
44
|
+
* @template TAttr - The event attributes type.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* class MyCollector extends BaseCollector<string, { page: string }> {
|
|
49
|
+
* constructor() {
|
|
50
|
+
* super('my-event', createSchema(z.string(), z.object({ page: z.string() })));
|
|
51
|
+
* }
|
|
52
|
+
* _initialize() {
|
|
53
|
+
* return of({ value: 'hello', attributes: { page: '/home' } });
|
|
54
|
+
* }
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
28
57
|
*/
|
|
29
58
|
export abstract class BaseCollector<
|
|
30
59
|
TValue extends AnyValue,
|
|
@@ -37,13 +66,33 @@ export abstract class BaseCollector<
|
|
|
37
66
|
z.infer<CollectorSchema<TValue, TAttr>>
|
|
38
67
|
>();
|
|
39
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Creates a new `BaseCollector`.
|
|
71
|
+
*
|
|
72
|
+
* @param name - Event name assigned to every emission (e.g. `'context-selected'`).
|
|
73
|
+
* @param schema - Zod schema used to validate each event before publishing.
|
|
74
|
+
*/
|
|
40
75
|
constructor(name: string, schema: CollectorSchema<TValue, TAttr>) {
|
|
41
76
|
this.#name = name;
|
|
42
77
|
this.#schema = schema;
|
|
43
78
|
}
|
|
44
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Returns an observable source of raw `{ value, attributes }` objects.
|
|
82
|
+
*
|
|
83
|
+
* @remarks
|
|
84
|
+
* Subclasses implement this to provide the domain-specific event stream.
|
|
85
|
+
* The base class wraps the output with the collector name and validates it.
|
|
86
|
+
*
|
|
87
|
+
* @returns An observable input of value/attribute pairs.
|
|
88
|
+
*/
|
|
45
89
|
abstract _initialize(): ObservableInput<{ value: TValue; attributes: TAttr }>;
|
|
46
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Subscribes to the source returned by {@link BaseCollector._initialize},
|
|
93
|
+
* validates each emission with the Zod schema, and publishes through the
|
|
94
|
+
* internal subject.
|
|
95
|
+
*/
|
|
47
96
|
initialize(): Promise<void> | void {
|
|
48
97
|
from(this._initialize())
|
|
49
98
|
.pipe(
|
|
@@ -59,6 +108,12 @@ export abstract class BaseCollector<
|
|
|
59
108
|
.subscribe(this.#subject);
|
|
60
109
|
}
|
|
61
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Subscribes an observer to the validated event stream.
|
|
113
|
+
*
|
|
114
|
+
* @param observer - Partial observer receiving validated events.
|
|
115
|
+
* @returns An `Unsubscribable` handle.
|
|
116
|
+
*/
|
|
62
117
|
subscribe(observer: Partial<Observer<z.infer<CollectorSchema<TValue, TAttr>>>>): Unsubscribable {
|
|
63
118
|
return this.#subject.subscribe(observer);
|
|
64
119
|
}
|
|
@@ -10,17 +10,28 @@ import {
|
|
|
10
10
|
extractContextMetadata,
|
|
11
11
|
} from './utils/extractContextMetadata.js';
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* The schema of the data to be sent.
|
|
15
|
-
*/
|
|
13
|
+
/** Zod schema for the `context-selected` event (value + attributes). */
|
|
16
14
|
const eventSchema = createSchema(
|
|
17
15
|
contextSchema,
|
|
18
16
|
z.object({ previous: contextSchema, appKey: z.string().optional() }),
|
|
19
17
|
);
|
|
20
18
|
|
|
21
19
|
/**
|
|
22
|
-
* Collector
|
|
23
|
-
*
|
|
20
|
+
* Collector that emits an analytics event whenever the active Fusion context changes.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* Listens to `IContextProvider.currentContext$`, de-duplicates by context ID,
|
|
24
|
+
* pairs consecutive values, and emits both the new and previous context
|
|
25
|
+
* metadata. The current application key is included in attributes.
|
|
26
|
+
*
|
|
27
|
+
* Register via:
|
|
28
|
+
* ```ts
|
|
29
|
+
* builder.setCollector('context-selected', async (args) => {
|
|
30
|
+
* const ctx = await args.requireInstance('context');
|
|
31
|
+
* const app = await args.requireInstance('app');
|
|
32
|
+
* return new ContextSelectedCollector(ctx, app);
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
24
35
|
*/
|
|
25
36
|
export class ContextSelectedCollector
|
|
26
37
|
extends BaseCollector<ContextItemType, { previous?: ContextItemType; appKey?: string }>
|
|
@@ -29,6 +40,10 @@ export class ContextSelectedCollector
|
|
|
29
40
|
#contextProvider: IContextProvider;
|
|
30
41
|
#appProvider: AppModuleProvider;
|
|
31
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @param contextProvider - Fusion context module provider to observe.
|
|
45
|
+
* @param appProvider - Fusion app module provider for the current app key.
|
|
46
|
+
*/
|
|
32
47
|
constructor(contextProvider: IContextProvider, appProvider: AppModuleProvider) {
|
|
33
48
|
super('context-selected', eventSchema);
|
|
34
49
|
this.#contextProvider = contextProvider;
|
package/src/collectors/index.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports for built-in analytics collectors.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Import from `'@equinor/fusion-framework-module-analytics/collectors'`.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
1
9
|
export { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
|
|
2
10
|
export { ContextSelectedCollector } from './ContextSelectedCollector.js';
|
|
3
11
|
export { AppSelectedCollector } from './AppSelectedCollector.js';
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { AppManifest, CurrentApp } from '@equinor/fusion-framework-module-app';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Zod schema for an object containing an optional `appKey` string.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Used by {@link AppSelectedCollector} to validate the event body.
|
|
9
|
+
*/
|
|
6
10
|
export const appKeySchema = z
|
|
7
11
|
.object({
|
|
8
12
|
appKey: z.string().optional(),
|
|
@@ -10,8 +14,13 @@ export const appKeySchema = z
|
|
|
10
14
|
.optional()
|
|
11
15
|
.nullable();
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Zod schema for a Fusion application metadata object.
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* Validates core app fields (appKey, displayName, type) and optional build
|
|
22
|
+
* and category information. Used by {@link AppLoadedCollector}.
|
|
23
|
+
*/
|
|
15
24
|
export const appSchema = z
|
|
16
25
|
.object({
|
|
17
26
|
appKey: z.string(),
|
|
@@ -23,16 +32,30 @@ export const appSchema = z
|
|
|
23
32
|
})
|
|
24
33
|
.optional();
|
|
25
34
|
|
|
35
|
+
/** Inferred type from {@link appKeySchema}. */
|
|
26
36
|
export type AppKeyType = z.infer<typeof appKeySchema>;
|
|
27
37
|
|
|
38
|
+
/** Inferred type from {@link appSchema}. */
|
|
28
39
|
export type AppItemType = z.infer<typeof appSchema>;
|
|
29
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Extracts app-key metadata from a `CurrentApp` instance.
|
|
43
|
+
*
|
|
44
|
+
* @param app - The current app object.
|
|
45
|
+
* @returns An object containing the optional `appKey`.
|
|
46
|
+
*/
|
|
30
47
|
export const extractAppKeyMetadata = (app: CurrentApp): z.input<typeof appKeySchema> => {
|
|
31
48
|
return {
|
|
32
49
|
appKey: app?.appKey,
|
|
33
50
|
};
|
|
34
51
|
};
|
|
35
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Extracts detailed app metadata from an `AppManifest` for analytics events.
|
|
55
|
+
*
|
|
56
|
+
* @param app - The application manifest.
|
|
57
|
+
* @returns An object with appKey, displayName, type, and optional build/category info.
|
|
58
|
+
*/
|
|
36
59
|
export const extractAppMetadata = (app: AppManifest): z.input<typeof appSchema> => {
|
|
37
60
|
return {
|
|
38
61
|
appKey: app.appKey,
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ContextItem } from '@equinor/fusion-framework-module-context';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Zod schema for a Fusion context metadata object.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Validates core context fields (id, type) and optional title, externalId,
|
|
9
|
+
* and source. Used by {@link ContextSelectedCollector} and {@link AppLoadedCollector}.
|
|
10
|
+
*/
|
|
6
11
|
export const contextSchema = z
|
|
7
12
|
.object({
|
|
8
13
|
id: z.string(),
|
|
@@ -14,8 +19,15 @@ export const contextSchema = z
|
|
|
14
19
|
.optional()
|
|
15
20
|
.nullable();
|
|
16
21
|
|
|
22
|
+
/** Inferred type from {@link contextSchema}. */
|
|
17
23
|
export type ContextItemType = z.infer<typeof contextSchema>;
|
|
18
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
|
+
*/
|
|
19
31
|
export const extractContextMetadata = (context: ContextItem): z.input<typeof contextSchema> => {
|
|
20
32
|
return {
|
|
21
33
|
id: context.id,
|
package/src/enable-analytics.ts
CHANGED
|
@@ -3,13 +3,29 @@ import type { IModulesConfigurator } from '@equinor/fusion-framework-module';
|
|
|
3
3
|
import { module } from './module.js';
|
|
4
4
|
import type { IAnalyticsConfigurator } from './AnalyticsConfigurator.interface.js';
|
|
5
5
|
|
|
6
|
+
/** Callback invoked with the {@link IAnalyticsConfigurator} to register adapters and collectors. */
|
|
6
7
|
type AnalyticsBuilderCallback = (builder: IAnalyticsConfigurator) => void | Promise<void>;
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* Enables analytics
|
|
10
|
+
* Enables the analytics module on a Fusion Framework module configurator.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* Call this helper during application or portal configuration to register
|
|
13
|
+
* adapters and collectors for analytics tracking.
|
|
14
|
+
*
|
|
15
|
+
* @param configurator - The module configurator instance to attach analytics to.
|
|
16
|
+
* @param callback - Optional callback to register adapters and collectors on the analytics builder.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { enableAnalytics } from '@equinor/fusion-framework-module-analytics';
|
|
21
|
+
* import { ConsoleAnalyticsAdapter } from '@equinor/fusion-framework-module-analytics/adapters';
|
|
22
|
+
*
|
|
23
|
+
* const configure = (configurator) => {
|
|
24
|
+
* enableAnalytics(configurator, (builder) => {
|
|
25
|
+
* builder.setAdapter('console', async () => new ConsoleAnalyticsAdapter());
|
|
26
|
+
* });
|
|
27
|
+
* };
|
|
28
|
+
* ```
|
|
13
29
|
*/
|
|
14
30
|
export const enableAnalytics = (
|
|
15
31
|
// biome-ignore lint/suspicious/noExplicitAny: must be any to support all module types
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@equinor/fusion-framework-module-analytics` — Fusion Framework module for
|
|
3
|
+
* collecting and exporting application analytics using OpenTelemetry standards.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* This package provides a pluggable adapter/collector architecture:
|
|
7
|
+
*
|
|
8
|
+
* - **Adapters** receive analytics events and forward them to a backend
|
|
9
|
+
* (e.g. console, OTLP endpoint).
|
|
10
|
+
* - **Collectors** observe application state (context changes, app loads) and
|
|
11
|
+
* emit structured {@link AnalyticsEvent} instances.
|
|
12
|
+
*
|
|
13
|
+
* Import adapters from `'@equinor/fusion-framework-module-analytics/adapters'`,
|
|
14
|
+
* collectors from `'@equinor/fusion-framework-module-analytics/collectors'`,
|
|
15
|
+
* and log exporters from `'@equinor/fusion-framework-module-analytics/logExporters'`.
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
|
|
1
20
|
export { module as analyticsModule, AnalyticsModule } from './module.js';
|
|
2
21
|
|
|
3
22
|
export { AnalyticsEvent, AnyValue, AnyValueMap } from './types.js';
|
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
import type { IHttpClient } from '@equinor/fusion-framework-module-http';
|
|
12
12
|
import { HttpClientExporterTransport } from './HttpClientExporterTransport.js';
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Returns shared default OTLP exporter configuration values.
|
|
16
|
+
*
|
|
17
|
+
* @returns Default timeout, concurrency limit, and compression settings.
|
|
18
|
+
*/
|
|
15
19
|
function getSharedConfigurationDefaults(): OtlpSharedConfiguration {
|
|
16
20
|
return {
|
|
17
21
|
timeoutMillis: 10000,
|
|
@@ -21,14 +25,32 @@ function getSharedConfigurationDefaults(): OtlpSharedConfiguration {
|
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
28
|
+
* OTLP log exporter that uses a Fusion `IHttpClient` as its HTTP transport.
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* Extends `OTLPExporterBase` and substitutes the default `fetch`-based transport
|
|
32
|
+
* with {@link HttpClientExporterTransport}, routing log record payloads
|
|
33
|
+
* through the Fusion HTTP module’s configured client (which may include
|
|
34
|
+
* authentication headers, interceptors, and service‑discovery routing).
|
|
35
|
+
*
|
|
36
|
+
* Typically used with {@link FusionAnalyticsAdapter} when the portal provides
|
|
37
|
+
* an HTTP client via service discovery.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { FusionOTLPLogExporter } from '@equinor/fusion-framework-module-analytics/logExporters';
|
|
42
|
+
*
|
|
43
|
+
* const httpClient = await serviceDiscovery.createClient('analytics');
|
|
44
|
+
* const exporter = new FusionOTLPLogExporter(httpClient);
|
|
45
|
+
* ```
|
|
27
46
|
*/
|
|
28
47
|
export class FusionOTLPLogExporter
|
|
29
48
|
extends OTLPExporterBase<ReadableLogRecord[]>
|
|
30
49
|
implements LogRecordExporter
|
|
31
50
|
{
|
|
51
|
+
/**
|
|
52
|
+
* @param httpClient - A Fusion `IHttpClient` used for outbound HTTP transport.
|
|
53
|
+
*/
|
|
32
54
|
constructor(httpClient: IHttpClient) {
|
|
33
55
|
super(
|
|
34
56
|
createOtlpNetworkExportDelegate(
|
|
@@ -2,6 +2,12 @@ import type { IExporterTransport, ExportResponse } from '@opentelemetry/otlp-exp
|
|
|
2
2
|
|
|
3
3
|
import type { IHttpClient } from '@equinor/fusion-framework-module-http';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Checks whether a failed HTTP status code is eligible for automatic retry.
|
|
7
|
+
*
|
|
8
|
+
* @param statusCode - HTTP response status code.
|
|
9
|
+
* @returns `true` for 429, 502, 503, and 504.
|
|
10
|
+
*/
|
|
5
11
|
function isExportRetryable(statusCode: number): boolean {
|
|
6
12
|
// Status codes of when we should consider retrying.
|
|
7
13
|
// 429 Too Many Requests
|
|
@@ -12,7 +18,12 @@ function isExportRetryable(statusCode: number): boolean {
|
|
|
12
18
|
return retryCodes.includes(statusCode);
|
|
13
19
|
}
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Parses an HTTP `Retry-After` header value into milliseconds.
|
|
23
|
+
*
|
|
24
|
+
* @param retryAfter - Raw header value (integer seconds or HTTP-date).
|
|
25
|
+
* @returns Delay in milliseconds, `-1` for non-positive integers, or `undefined` if absent.
|
|
26
|
+
*/
|
|
16
27
|
function parseRetryAfterToMills(retryAfter?: string | undefined | null): number | undefined {
|
|
17
28
|
if (retryAfter == null) {
|
|
18
29
|
return undefined;
|
|
@@ -32,19 +43,36 @@ function parseRetryAfterToMills(retryAfter?: string | undefined | null): number
|
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
/**
|
|
35
|
-
*
|
|
46
|
+
* OpenTelemetry exporter transport that posts serialised log records using a
|
|
47
|
+
* Fusion `IHttpClient`.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* Implements `IExporterTransport` from `@opentelemetry/otlp-exporter-base`.
|
|
51
|
+
* The transport honours the exporter’s timeout via `AbortController` and
|
|
52
|
+
* inspects response status codes to report retryable failures (429, 502–504).
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* const transport = new HttpClientExporterTransport(httpClient, '/v1/logs');
|
|
57
|
+
* ```
|
|
36
58
|
*/
|
|
37
59
|
export class HttpClientExporterTransport implements IExporterTransport {
|
|
60
|
+
/**
|
|
61
|
+
* @param httpClient - Fusion HTTP client for outgoing requests.
|
|
62
|
+
* @param path - URL path appended to the client’s base URL (default `'/v1/logs'`).
|
|
63
|
+
*/
|
|
38
64
|
constructor(
|
|
39
65
|
private httpClient: IHttpClient,
|
|
40
66
|
private path: string = '/v1/logs',
|
|
41
67
|
) {}
|
|
42
68
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Sends serialised log record data to the configured endpoint.
|
|
71
|
+
*
|
|
72
|
+
* @param data - Serialised OTLP payload as a `Uint8Array`.
|
|
73
|
+
* @param timeoutMillis - Maximum time in milliseconds before the request is aborted.
|
|
74
|
+
* @returns An `ExportResponse` indicating success, retryable failure, or permanent failure.
|
|
75
|
+
*/
|
|
48
76
|
async send(data: Uint8Array, timeoutMillis: number): Promise<ExportResponse> {
|
|
49
77
|
const abortController = new AbortController();
|
|
50
78
|
const timeout = setTimeout(() => abortController.abort(), timeoutMillis);
|
|
@@ -83,6 +111,7 @@ export class HttpClientExporterTransport implements IExporterTransport {
|
|
|
83
111
|
clearTimeout(timeout);
|
|
84
112
|
}
|
|
85
113
|
}
|
|
114
|
+
/** No-op; the HTTP client does not hold persistent connections. */
|
|
86
115
|
shutdown(): void {
|
|
87
116
|
// intentionally left empty, nothing to do.
|
|
88
117
|
}
|
|
@@ -1,2 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports for OTLP log exporters used by analytics adapters.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Import from `'@equinor/fusion-framework-module-analytics/logExporters'`.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
1
9
|
export { FusionOTLPLogExporter } from './fusionOTLPLogExporter/FusionOTLPLogExporter.js';
|
|
2
10
|
export { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
|