@equinor/fusion-framework-module-analytics 3.1.0 → 3.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/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +11 -8
- package/CHANGELOG.md +0 -386
- package/docs/adapters.md +0 -79
- package/docs/collectors.md +0 -91
- package/docs/testing.md +0 -87
- package/docs/tracking-events.md +0 -16
- package/src/AnalyticsConfigurator.interface.ts +0 -77
- package/src/AnalyticsConfigurator.ts +0 -128
- package/src/AnalyticsProvider.interface.ts +0 -44
- package/src/AnalyticsProvider.ts +0 -150
- package/src/__tests__/MockAnalyticsAdapter.test.ts +0 -127
- package/src/__tests__/mock/analytics-mock-adapter.test.ts +0 -79
- package/src/adapters/AnalyticsAdapter.interface.ts +0 -36
- package/src/adapters/ConsoleAnalyticsAdapter.ts +0 -37
- package/src/adapters/FusionAnalyticsAdapter.ts +0 -95
- package/src/adapters/index.ts +0 -11
- package/src/collectors/AnalyticsCollector.interface.ts +0 -27
- package/src/collectors/AppLoadedCollector.ts +0 -94
- package/src/collectors/AppSelectedCollector.ts +0 -70
- package/src/collectors/ContextSelectedCollector.ts +0 -83
- package/src/collectors/create-schema.ts +0 -123
- package/src/collectors/index.ts +0 -12
- package/src/collectors/utils/app-key-schema.ts +0 -17
- package/src/collectors/utils/app-schema.ts +0 -22
- package/src/collectors/utils/context-schema.ts +0 -22
- package/src/collectors/utils/extract-app-key-metadata.ts +0 -15
- package/src/collectors/utils/extract-app-metadata.ts +0 -20
- package/src/collectors/utils/extract-context-metadata.ts +0 -19
- package/src/enable-analytics.ts +0 -44
- package/src/index.ts +0 -30
- package/src/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.ts +0 -71
- package/src/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.ts +0 -123
- package/src/logExporters/index.ts +0 -10
- package/src/mock/MockAnalyticsAdapter.ts +0 -178
- package/src/mock/index.ts +0 -29
- package/src/module.ts +0 -59
- package/src/types.ts +0 -60
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -29
- package/vitest.config.ts +0 -11
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { ModulesConfigurator } from '@equinor/fusion-framework-module';
|
|
3
|
-
import { Subject } from 'rxjs';
|
|
4
|
-
|
|
5
|
-
import { enableAnalytics } from '../../enable-analytics.js';
|
|
6
|
-
import { ConsoleAnalyticsAdapter } from '../../adapters/ConsoleAnalyticsAdapter.js';
|
|
7
|
-
import { MockAnalyticsAdapter } from '../../mock/MockAnalyticsAdapter.js';
|
|
8
|
-
import type { IAnalyticsConfigurator } from '../../AnalyticsConfigurator.interface.js';
|
|
9
|
-
import type { IAnalyticsProvider } from '../../AnalyticsProvider.interface.js';
|
|
10
|
-
import type { AnalyticsEvent } from '../../types.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Initializes the analytics module through the real module system, with a
|
|
14
|
-
* `MockAnalyticsAdapter` registered alongside whatever the test configures.
|
|
15
|
-
*
|
|
16
|
-
* @remarks
|
|
17
|
-
* Deliberately avoids hand-building an `AnalyticsProvider`. Testing the
|
|
18
|
-
* adapter's own logic in isolation (see `MockAnalyticsAdapter.test.ts`) can't
|
|
19
|
-
* prove it actually receives events through the real configure -> initialize
|
|
20
|
-
* -> collector/adapter dispatch pipeline every other adapter goes through.
|
|
21
|
-
*
|
|
22
|
-
* @param configure - Optional callback to register additional adapters/collectors.
|
|
23
|
-
* @returns The real `IAnalyticsProvider` instance and the recording adapter.
|
|
24
|
-
*/
|
|
25
|
-
const initializeWith = async (
|
|
26
|
-
configure?: (builder: IAnalyticsConfigurator) => void,
|
|
27
|
-
): Promise<{ provider: IAnalyticsProvider; recorder: MockAnalyticsAdapter }> => {
|
|
28
|
-
const recorder = new MockAnalyticsAdapter();
|
|
29
|
-
const configurator = new ModulesConfigurator([]);
|
|
30
|
-
|
|
31
|
-
enableAnalytics(configurator, (builder) => {
|
|
32
|
-
builder.setAdapter('mock', async () => recorder);
|
|
33
|
-
configure?.(builder);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const instances = await configurator.initialize();
|
|
37
|
-
const provider = (instances as unknown as { analytics: IAnalyticsProvider }).analytics;
|
|
38
|
-
|
|
39
|
-
return { provider, recorder };
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
describe('MockAnalyticsAdapter (through the real analytics module)', () => {
|
|
43
|
-
it('observes events pushed via provider.trackAnalytic', async () => {
|
|
44
|
-
const { provider, recorder } = await initializeWith();
|
|
45
|
-
|
|
46
|
-
provider.trackAnalytic({ name: 'button-click', value: 'save' });
|
|
47
|
-
|
|
48
|
-
expect(recorder.getAnalytics('button-click')).toHaveLength(1);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('observes events emitted by a real registered collector', async () => {
|
|
52
|
-
const clicks$ = new Subject<AnalyticsEvent>();
|
|
53
|
-
const { recorder } = await initializeWith((builder) => {
|
|
54
|
-
builder.setCollector('clicks', async () => clicks$);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
clicks$.next({ name: 'window-click', value: 42 });
|
|
58
|
-
|
|
59
|
-
const event = await recorder.waitForAnalytic('window-click');
|
|
60
|
-
expect(event.value).toBe(42);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('does not interfere with other adapters registered alongside it', async () => {
|
|
64
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
65
|
-
const { provider, recorder } = await initializeWith((builder) => {
|
|
66
|
-
builder.setAdapter('console', async () => new ConsoleAnalyticsAdapter());
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
provider.trackAnalytic({ name: 'page-view', value: null });
|
|
70
|
-
|
|
71
|
-
expect(recorder.getAnalytics('page-view')).toHaveLength(1);
|
|
72
|
-
expect(logSpy).toHaveBeenCalledWith('Analytics::Adapter::Console', {
|
|
73
|
-
name: 'page-view',
|
|
74
|
-
value: null,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
logSpy.mockRestore();
|
|
78
|
-
});
|
|
79
|
-
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { AnalyticsEvent } from '../types.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Contract for an analytics adapter that receives events and exports them to a backend.
|
|
5
|
-
*
|
|
6
|
-
* @remarks
|
|
7
|
-
* Implement this interface to create a custom adapter. Register it via
|
|
8
|
-
* {@link IAnalyticsConfigurator.setAdapter}. The adapter must also implement
|
|
9
|
-
* `Disposable` for resource cleanup.
|
|
10
|
-
*
|
|
11
|
-
* @template T - The analytics event type handled by the adapter, defaults to {@link AnalyticsEvent}.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* class MyAdapter implements IAnalyticsAdapter {
|
|
16
|
-
* registerAnalytic(event) { fetch('/analytics', { method: 'POST', body: JSON.stringify(event) }); }
|
|
17
|
-
* [Symbol.dispose]() { /* cleanup *\/ }
|
|
18
|
-
* }
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export interface IAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent> extends Disposable {
|
|
22
|
-
/**
|
|
23
|
-
* Initializes the analytics adapter for setup and configuration.
|
|
24
|
-
*
|
|
25
|
-
* @returns Promise that resolves when initialization is complete, or void for synchronous initialization.
|
|
26
|
-
*/
|
|
27
|
-
initialize?(): Promise<void> | void;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Exports analytics event to the configured backend.
|
|
31
|
-
*
|
|
32
|
-
* @param events - Array of analytics events to export.
|
|
33
|
-
* @returns Promise that resolves when export is complete.
|
|
34
|
-
*/
|
|
35
|
-
registerAnalytic(event: T): Promise<void> | void;
|
|
36
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { IAnalyticsAdapter } from './AnalyticsAdapter.interface.js';
|
|
2
|
-
import type { AnalyticsEvent } from '../types.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
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
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
export class ConsoleAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent>
|
|
21
|
-
implements IAnalyticsAdapter
|
|
22
|
-
{
|
|
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
|
-
*/
|
|
29
|
-
registerAnalytic(event: T): Promise<void> | void {
|
|
30
|
-
console.log('Analytics::Adapter::Console', event);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** No teardown is required for this adapter. */
|
|
34
|
-
[Symbol.dispose]() {
|
|
35
|
-
// no-op
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import type { IAnalyticsAdapter } from './AnalyticsAdapter.interface.js';
|
|
2
|
-
import type { AnalyticsEvent } from '../types.js';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
LoggerProvider,
|
|
6
|
-
BatchLogRecordProcessor,
|
|
7
|
-
type ReadableLogRecord,
|
|
8
|
-
} from '@opentelemetry/sdk-logs';
|
|
9
|
-
import type { OTLPExporterBase } from '@opentelemetry/otlp-exporter-base';
|
|
10
|
-
import {
|
|
11
|
-
SeverityNumber,
|
|
12
|
-
type LogAttributes,
|
|
13
|
-
type Logger,
|
|
14
|
-
type LogRecord,
|
|
15
|
-
} from '@opentelemetry/api-logs';
|
|
16
|
-
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
17
|
-
import { version } from '../version.js';
|
|
18
|
-
import { v7 as uuid } from 'uuid';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Analytics adapter that forwards events to an OpenTelemetry log exporter.
|
|
22
|
-
*
|
|
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}
|
|
45
|
-
*/
|
|
46
|
-
export class FusionAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent>
|
|
47
|
-
implements IAnalyticsAdapter
|
|
48
|
-
{
|
|
49
|
-
#logExporter: OTLPExporterBase<ReadableLogRecord[]>;
|
|
50
|
-
#loggerProvider: LoggerProvider;
|
|
51
|
-
#logger: Logger;
|
|
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
|
-
*/
|
|
60
|
-
constructor(args: { portalId: string; logExporter: OTLPExporterBase<ReadableLogRecord[]> }) {
|
|
61
|
-
this.#logExporter = args.logExporter;
|
|
62
|
-
|
|
63
|
-
this.#loggerProvider = new LoggerProvider({
|
|
64
|
-
processors: [new BatchLogRecordProcessor({ exporter: this.#logExporter })],
|
|
65
|
-
resource: resourceFromAttributes({
|
|
66
|
-
'module.version': version,
|
|
67
|
-
'session.id': uuid(),
|
|
68
|
-
'portal.id': args.portalId,
|
|
69
|
-
}),
|
|
70
|
-
});
|
|
71
|
-
this.#logger = this.#loggerProvider.getLogger('fusion');
|
|
72
|
-
}
|
|
73
|
-
|
|
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
|
-
*/
|
|
80
|
-
registerAnalytic(event: T): Promise<void> | void {
|
|
81
|
-
const logRecord: Partial<LogRecord> = {
|
|
82
|
-
eventName: event.name,
|
|
83
|
-
attributes: event.attributes as LogAttributes,
|
|
84
|
-
body: event.value,
|
|
85
|
-
severityNumber: SeverityNumber.INFO,
|
|
86
|
-
};
|
|
87
|
-
this.#logger.emit(logRecord);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Shuts down the log exporter and logger provider, flushing remaining records. */
|
|
91
|
-
[Symbol.dispose]() {
|
|
92
|
-
this.#logExporter.shutdown();
|
|
93
|
-
this.#loggerProvider.shutdown();
|
|
94
|
-
}
|
|
95
|
-
}
|
package/src/adapters/index.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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
|
-
*/
|
|
9
|
-
export { IAnalyticsAdapter } from './AnalyticsAdapter.interface.js';
|
|
10
|
-
export { ConsoleAnalyticsAdapter } from './ConsoleAnalyticsAdapter.js';
|
|
11
|
-
export { FusionAnalyticsAdapter } from './FusionAnalyticsAdapter.js';
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { Subscribable } from 'rxjs';
|
|
2
|
-
import type { AnalyticsEvent } from '../types.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Contract for an analytics collector that observes application state and emits
|
|
6
|
-
* structured analytics events.
|
|
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}.
|
|
17
|
-
*/
|
|
18
|
-
export interface IAnalyticsCollector<T extends AnalyticsEvent = AnalyticsEvent>
|
|
19
|
-
extends Subscribable<T> {
|
|
20
|
-
/**
|
|
21
|
-
* Initializes the analytics collector for setup and configuration.
|
|
22
|
-
* This method is optional - if not implemented, the collector should be directly subscribable.
|
|
23
|
-
*
|
|
24
|
-
* @returns Promise that resolves when initialization is complete, or void for synchronous initialization.
|
|
25
|
-
*/
|
|
26
|
-
initialize?(): Promise<void> | void;
|
|
27
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
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';
|
|
6
|
-
import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
AppModulesInstance,
|
|
10
|
-
AppManifest,
|
|
11
|
-
AppModuleProvider,
|
|
12
|
-
} from '@equinor/fusion-framework-module-app';
|
|
13
|
-
import type { IEventModuleProvider } from '@equinor/fusion-framework-module-event';
|
|
14
|
-
|
|
15
|
-
import { type ObservableInput, Subject } from 'rxjs';
|
|
16
|
-
import { z } from 'zod';
|
|
17
|
-
import type { ContextModule } from '@equinor/fusion-framework-module-context';
|
|
18
|
-
|
|
19
|
-
const EVENT_NAME = 'onAppModulesLoaded';
|
|
20
|
-
|
|
21
|
-
/** Zod schema for the `app-loaded` event (value + attributes). */
|
|
22
|
-
const eventSchema = createSchema(appSchema, z.object({ context: contextSchema }));
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Collector that emits an analytics event whenever an application’s modules
|
|
26
|
-
* finish loading.
|
|
27
|
-
*
|
|
28
|
-
* @remarks
|
|
29
|
-
* Listens to the framework `onAppModulesLoaded` event, extracts application
|
|
30
|
-
* manifest metadata, and includes the current context (if available) in the
|
|
31
|
-
* event attributes.
|
|
32
|
-
*
|
|
33
|
-
* Register via:
|
|
34
|
-
* ```ts
|
|
35
|
-
* builder.setCollector('app-loaded', async (args) => {
|
|
36
|
-
* const event = await args.requireInstance('event');
|
|
37
|
-
* const app = await args.requireInstance('app');
|
|
38
|
-
* return new AppLoadedCollector(event, app);
|
|
39
|
-
* });
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
export class AppLoadedCollector
|
|
43
|
-
extends BaseCollector<AppItemType, { context?: ContextItemType }>
|
|
44
|
-
implements IAnalyticsCollector
|
|
45
|
-
{
|
|
46
|
-
#eventProvider: IEventModuleProvider;
|
|
47
|
-
#appProvider: AppModuleProvider;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* @param eventProvider - Fusion event module provider to listen for app-loaded events.
|
|
51
|
-
* @param appProvider - Fusion app module provider for fallback manifest data.
|
|
52
|
-
*/
|
|
53
|
-
constructor(eventProvider: IEventModuleProvider, appProvider: AppModuleProvider) {
|
|
54
|
-
super('app-loaded', eventSchema);
|
|
55
|
-
this.#eventProvider = eventProvider;
|
|
56
|
-
this.#appProvider = appProvider;
|
|
57
|
-
}
|
|
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
|
-
*/
|
|
65
|
-
_initialize(): ObservableInput<{
|
|
66
|
-
value: AppItemType;
|
|
67
|
-
attributes: { context?: ContextItemType };
|
|
68
|
-
}> {
|
|
69
|
-
const subject = new Subject<{
|
|
70
|
-
value: AppItemType;
|
|
71
|
-
attributes: { context?: ContextItemType };
|
|
72
|
-
}>();
|
|
73
|
-
this.#eventProvider.addEventListener(EVENT_NAME, (event) => {
|
|
74
|
-
// Fallback to appProvider for manifest if app is not updated with latest
|
|
75
|
-
// payload for onAppModulesLoaded (missing manifest).
|
|
76
|
-
const manifest =
|
|
77
|
-
(event.detail.manifest as AppManifest) ?? this.#appProvider.current?.manifest;
|
|
78
|
-
const modules = event.detail.modules as AppModulesInstance<[ContextModule]>;
|
|
79
|
-
|
|
80
|
-
const data = {
|
|
81
|
-
value: manifest && extractAppMetadata(manifest),
|
|
82
|
-
attributes: {
|
|
83
|
-
context:
|
|
84
|
-
modules.context?.currentContext &&
|
|
85
|
-
extractContextMetadata(modules.context.currentContext),
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
subject.next(data);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
return subject;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { map, type ObservableInput, pairwise } from 'rxjs';
|
|
2
|
-
import type { IAnalyticsCollector } from './AnalyticsCollector.interface.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';
|
|
6
|
-
|
|
7
|
-
import type { AppModuleProvider } from '@equinor/fusion-framework-module-app';
|
|
8
|
-
|
|
9
|
-
import { z } from 'zod';
|
|
10
|
-
|
|
11
|
-
/** Zod schema for the `app-selected` event (value + attributes). */
|
|
12
|
-
const eventSchema = createSchema(appKeySchema, z.object({ previous: appKeySchema }));
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Collector that emits an analytics event whenever the active Fusion application changes.
|
|
16
|
-
*
|
|
17
|
-
* @remarks
|
|
18
|
-
* Listens to `AppModuleProvider.current$`, pairs consecutive values, and emits
|
|
19
|
-
* both the new and previous app key metadata.
|
|
20
|
-
*
|
|
21
|
-
* Register via:
|
|
22
|
-
* ```ts
|
|
23
|
-
* builder.setCollector('app-selected', async (args) => {
|
|
24
|
-
* const app = await args.requireInstance('app');
|
|
25
|
-
* return new AppSelectedCollector(app);
|
|
26
|
-
* });
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export class AppSelectedCollector
|
|
30
|
-
extends BaseCollector<AppKeyType, { previous?: AppKeyType }>
|
|
31
|
-
implements IAnalyticsCollector
|
|
32
|
-
{
|
|
33
|
-
#appProvider: AppModuleProvider;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @param appProvider - Fusion app module provider to observe.
|
|
37
|
-
*/
|
|
38
|
-
constructor(appProvider: AppModuleProvider) {
|
|
39
|
-
super('app-selected', eventSchema);
|
|
40
|
-
this.#appProvider = appProvider;
|
|
41
|
-
}
|
|
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
|
-
*/
|
|
49
|
-
_initialize(): ObservableInput<{
|
|
50
|
-
value: AppKeyType;
|
|
51
|
-
attributes: { previous?: AppKeyType };
|
|
52
|
-
}> {
|
|
53
|
-
// Pair each app-selection emission with the previously selected app
|
|
54
|
-
const appSelected$ = this.#appProvider.current$.pipe(pairwise());
|
|
55
|
-
|
|
56
|
-
// Map the [previous, next] pair into the analytics event shape
|
|
57
|
-
const data$ = appSelected$.pipe(
|
|
58
|
-
map(([prev, next]) => {
|
|
59
|
-
return {
|
|
60
|
-
value: extractAppKeyMetadata(next),
|
|
61
|
-
attributes: {
|
|
62
|
-
previous: prev && extractAppKeyMetadata(prev),
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}),
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
return data$;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { distinctUntilChanged, map, type ObservableInput, pairwise } from 'rxjs';
|
|
2
|
-
import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
|
|
3
|
-
import type { IContextProvider } from '@equinor/fusion-framework-module-context';
|
|
4
|
-
import type { AppModuleProvider } from '@equinor/fusion-framework-module-app';
|
|
5
|
-
import { z } from 'zod';
|
|
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';
|
|
9
|
-
|
|
10
|
-
/** Zod schema for the `context-selected` event (value + attributes). */
|
|
11
|
-
const eventSchema = createSchema(
|
|
12
|
-
contextSchema,
|
|
13
|
-
z.object({ previous: contextSchema, appKey: z.string().optional() }),
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Collector that emits an analytics event whenever the active Fusion context changes.
|
|
18
|
-
*
|
|
19
|
-
* @remarks
|
|
20
|
-
* Listens to `IContextProvider.currentContext$`, de-duplicates by context ID,
|
|
21
|
-
* pairs consecutive values, and emits both the new and previous context
|
|
22
|
-
* metadata. The current application key is included in attributes.
|
|
23
|
-
*
|
|
24
|
-
* Register via:
|
|
25
|
-
* ```ts
|
|
26
|
-
* builder.setCollector('context-selected', async (args) => {
|
|
27
|
-
* const ctx = await args.requireInstance('context');
|
|
28
|
-
* const app = await args.requireInstance('app');
|
|
29
|
-
* return new ContextSelectedCollector(ctx, app);
|
|
30
|
-
* });
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
|
-
export class ContextSelectedCollector
|
|
34
|
-
extends BaseCollector<ContextItemType, { previous?: ContextItemType; appKey?: string }>
|
|
35
|
-
implements IAnalyticsCollector
|
|
36
|
-
{
|
|
37
|
-
#contextProvider: IContextProvider;
|
|
38
|
-
#appProvider: AppModuleProvider;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* @param contextProvider - Fusion context module provider to observe.
|
|
42
|
-
* @param appProvider - Fusion app module provider for the current app key.
|
|
43
|
-
*/
|
|
44
|
-
constructor(contextProvider: IContextProvider, appProvider: AppModuleProvider) {
|
|
45
|
-
super('context-selected', eventSchema);
|
|
46
|
-
this.#contextProvider = contextProvider;
|
|
47
|
-
this.#appProvider = appProvider;
|
|
48
|
-
}
|
|
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
|
-
*/
|
|
56
|
-
_initialize(): ObservableInput<{
|
|
57
|
-
value: ContextItemType;
|
|
58
|
-
attributes: { previous?: ContextItemType; appKey?: string };
|
|
59
|
-
}> {
|
|
60
|
-
// Track context changes, pairing each new context with the previously selected one
|
|
61
|
-
const contextSelected$ = this.#contextProvider.currentContext$.pipe(
|
|
62
|
-
// Only emit when an actual change has happened.
|
|
63
|
-
distinctUntilChanged((prev, curr) => prev?.id === curr?.id),
|
|
64
|
-
// Provide both the old and the new value.
|
|
65
|
-
pairwise(),
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
// Map the [previous, next] pair into the analytics event shape
|
|
69
|
-
const data$ = contextSelected$.pipe(
|
|
70
|
-
map(([prev, next]) => {
|
|
71
|
-
return {
|
|
72
|
-
value: next && extractContextMetadata(next),
|
|
73
|
-
attributes: {
|
|
74
|
-
previous: prev && extractContextMetadata(prev),
|
|
75
|
-
appKey: this.#appProvider.current?.appKey,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}),
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
return data$;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import type { AnyValue, AnyValueMap, AnalyticsEvent } from '../types.js';
|
|
2
|
-
import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
|
|
3
|
-
|
|
4
|
-
import { from, map, type ObservableInput, Subject, type Observer, type Unsubscribable } from 'rxjs';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
|
|
7
|
-
/**
|
|
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? }`.
|
|
16
|
-
*/
|
|
17
|
-
export const createSchema = <TValue = AnyValue, TAttr = AnyValueMap>(
|
|
18
|
-
value: z.ZodSchema<TValue>,
|
|
19
|
-
attributes: z.ZodSchema<TAttr>,
|
|
20
|
-
) => {
|
|
21
|
-
return z.object({
|
|
22
|
-
name: z.string(),
|
|
23
|
-
value: value,
|
|
24
|
-
attributes: attributes.optional(),
|
|
25
|
-
});
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/** Inferred Zod schema type produced by {@link createSchema}. */
|
|
29
|
-
export type CollectorSchema<TValue = AnyValue, TAttr = AnyValueMap> = ReturnType<
|
|
30
|
-
typeof createSchema<TValue, TAttr>
|
|
31
|
-
>;
|
|
32
|
-
|
|
33
|
-
/**
|
|
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
|
-
* ```
|
|
57
|
-
*/
|
|
58
|
-
export abstract class BaseCollector<
|
|
59
|
-
TValue extends AnyValue,
|
|
60
|
-
TAttr extends AnyValueMap = AnyValueMap,
|
|
61
|
-
> implements IAnalyticsCollector<AnalyticsEvent<TValue, TAttr>>
|
|
62
|
-
{
|
|
63
|
-
#schema: CollectorSchema<TValue, TAttr>;
|
|
64
|
-
#name: string;
|
|
65
|
-
#subject: Subject<z.infer<CollectorSchema<TValue, TAttr>>> = new Subject<
|
|
66
|
-
z.infer<CollectorSchema<TValue, TAttr>>
|
|
67
|
-
>();
|
|
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
|
-
*/
|
|
75
|
-
constructor(name: string, schema: CollectorSchema<TValue, TAttr>) {
|
|
76
|
-
this.#name = name;
|
|
77
|
-
this.#schema = schema;
|
|
78
|
-
}
|
|
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
|
-
*/
|
|
89
|
-
abstract _initialize(): ObservableInput<{ value: TValue; attributes: TAttr }>;
|
|
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
|
-
*
|
|
96
|
-
* @returns Resolves once the source has been subscribed to.
|
|
97
|
-
*/
|
|
98
|
-
initialize(): Promise<void> | void {
|
|
99
|
-
// Shape each emission into the named/validated event before publishing
|
|
100
|
-
from(this._initialize())
|
|
101
|
-
.pipe(
|
|
102
|
-
map(({ value, attributes }) => {
|
|
103
|
-
return {
|
|
104
|
-
name: this.#name,
|
|
105
|
-
value: value,
|
|
106
|
-
attributes: attributes,
|
|
107
|
-
};
|
|
108
|
-
}),
|
|
109
|
-
map((x) => this.#schema.parse(x)),
|
|
110
|
-
)
|
|
111
|
-
.subscribe(this.#subject);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Subscribes an observer to the validated event stream.
|
|
116
|
-
*
|
|
117
|
-
* @param observer - Partial observer receiving validated events.
|
|
118
|
-
* @returns An `Unsubscribable` handle.
|
|
119
|
-
*/
|
|
120
|
-
subscribe(observer: Partial<Observer<z.infer<CollectorSchema<TValue, TAttr>>>>): Unsubscribable {
|
|
121
|
-
return this.#subject.subscribe(observer);
|
|
122
|
-
}
|
|
123
|
-
}
|
package/src/collectors/index.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
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
|
-
*/
|
|
9
|
-
export { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
|
|
10
|
-
export { ContextSelectedCollector } from './ContextSelectedCollector.js';
|
|
11
|
-
export { AppSelectedCollector } from './AppSelectedCollector.js';
|
|
12
|
-
export { AppLoadedCollector } from './AppLoadedCollector.js';
|