@equinor/fusion-framework-module-analytics 3.1.0-next.1 → 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/esm/version.js.map +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 -388
- 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,17 +0,0 @@
|
|
|
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>;
|
|
@@ -1,22 +0,0 @@
|
|
|
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,22 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Zod schema for a Fusion context metadata object.
|
|
5
|
-
*
|
|
6
|
-
* @remarks
|
|
7
|
-
* Validates core context fields (id, type) and optional title, externalId,
|
|
8
|
-
* and source. Used by {@link ContextSelectedCollector} and {@link AppLoadedCollector}.
|
|
9
|
-
*/
|
|
10
|
-
export const contextSchema = z
|
|
11
|
-
.object({
|
|
12
|
-
id: z.string(),
|
|
13
|
-
type: z.string(),
|
|
14
|
-
title: z.string().optional(),
|
|
15
|
-
externalId: z.string().optional(),
|
|
16
|
-
source: z.string().optional(),
|
|
17
|
-
})
|
|
18
|
-
.optional()
|
|
19
|
-
.nullable();
|
|
20
|
-
|
|
21
|
-
/** Inferred type from {@link contextSchema}. */
|
|
22
|
-
export type ContextItemType = z.infer<typeof contextSchema>;
|
|
@@ -1,15 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,19 +0,0 @@
|
|
|
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
|
-
/**
|
|
6
|
-
* Extracts context metadata from a `ContextItem` for analytics events.
|
|
7
|
-
*
|
|
8
|
-
* @param context - The Fusion context item.
|
|
9
|
-
* @returns An object with id, type, and optional title, externalId, and source.
|
|
10
|
-
*/
|
|
11
|
-
export const extractContextMetadata = (context: ContextItem): z.input<typeof contextSchema> => {
|
|
12
|
-
return {
|
|
13
|
-
id: context.id,
|
|
14
|
-
externalId: context.externalId ?? undefined,
|
|
15
|
-
title: context.title ?? undefined,
|
|
16
|
-
type: context.type.id,
|
|
17
|
-
source: context.source ?? undefined,
|
|
18
|
-
};
|
|
19
|
-
};
|
package/src/enable-analytics.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { IModulesConfigurator } from '@equinor/fusion-framework-module';
|
|
2
|
-
|
|
3
|
-
import { module } from './module.js';
|
|
4
|
-
import type { IAnalyticsConfigurator } from './AnalyticsConfigurator.interface.js';
|
|
5
|
-
|
|
6
|
-
/** Callback invoked with the {@link IAnalyticsConfigurator} to register adapters and collectors. */
|
|
7
|
-
type AnalyticsBuilderCallback = (builder: IAnalyticsConfigurator) => void | Promise<void>;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Enables the analytics module on a Fusion Framework module configurator.
|
|
11
|
-
*
|
|
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
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export const enableAnalytics = (
|
|
31
|
-
// biome-ignore lint/suspicious/noExplicitAny: must be any to support all module types
|
|
32
|
-
configurator: IModulesConfigurator<any, any>,
|
|
33
|
-
callback?: AnalyticsBuilderCallback,
|
|
34
|
-
): void => {
|
|
35
|
-
configurator.addConfig({
|
|
36
|
-
module,
|
|
37
|
-
configure: async (builder) => {
|
|
38
|
-
// Only invoke the callback when one was supplied by the caller
|
|
39
|
-
if (callback) {
|
|
40
|
-
await Promise.resolve(callback(builder));
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
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
|
-
|
|
20
|
-
export { module as analyticsModule, AnalyticsModule } from './module.js';
|
|
21
|
-
|
|
22
|
-
export { AnalyticsEvent, AnyValue, AnyValueMap } from './types.js';
|
|
23
|
-
|
|
24
|
-
export { AnalyticsProvider } from './AnalyticsProvider.js';
|
|
25
|
-
export { IAnalyticsProvider } from './AnalyticsProvider.interface.js';
|
|
26
|
-
|
|
27
|
-
export { AnalyticsConfig, IAnalyticsConfigurator } from './AnalyticsConfigurator.interface.js';
|
|
28
|
-
export { AnalyticsConfigurator } from './AnalyticsConfigurator.js';
|
|
29
|
-
|
|
30
|
-
export { enableAnalytics } from './enable-analytics.js';
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { LogRecordExporter, ReadableLogRecord } from '@opentelemetry/sdk-logs';
|
|
2
|
-
|
|
3
|
-
import { JsonLogsSerializer, LogsExporterMetricsHelper } from '@opentelemetry/otlp-transformer';
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
createOtlpNetworkExportDelegate,
|
|
7
|
-
ExporterMetrics,
|
|
8
|
-
OTLPExporterBase,
|
|
9
|
-
type OtlpSharedConfiguration,
|
|
10
|
-
} from '@opentelemetry/otlp-exporter-base';
|
|
11
|
-
|
|
12
|
-
import type { IHttpClient } from '@equinor/fusion-framework-module-http';
|
|
13
|
-
import { HttpClientExporterTransport } from './HttpClientExporterTransport.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Returns shared default OTLP exporter configuration values.
|
|
17
|
-
*
|
|
18
|
-
* @returns Default timeout, concurrency limit, and compression settings.
|
|
19
|
-
*/
|
|
20
|
-
function getSharedConfigurationDefaults(): OtlpSharedConfiguration {
|
|
21
|
-
return {
|
|
22
|
-
timeoutMillis: 10000,
|
|
23
|
-
concurrencyLimit: 30,
|
|
24
|
-
compression: 'none',
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* OTLP log exporter that uses a Fusion `IHttpClient` as its HTTP transport.
|
|
30
|
-
*
|
|
31
|
-
* @remarks
|
|
32
|
-
* Extends `OTLPExporterBase` and substitutes the default `fetch`-based transport
|
|
33
|
-
* with {@link HttpClientExporterTransport}, routing log record payloads
|
|
34
|
-
* through the Fusion HTTP module’s configured client (which may include
|
|
35
|
-
* authentication headers, interceptors, and service‑discovery routing).
|
|
36
|
-
*
|
|
37
|
-
* Typically used with {@link FusionAnalyticsAdapter} when the portal provides
|
|
38
|
-
* an HTTP client via service discovery.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```ts
|
|
42
|
-
* import { FusionOTLPLogExporter } from '@equinor/fusion-framework-module-analytics/logExporters';
|
|
43
|
-
*
|
|
44
|
-
* const httpClient = await serviceDiscovery.createClient('analytics');
|
|
45
|
-
* const exporter = new FusionOTLPLogExporter(httpClient);
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
export class FusionOTLPLogExporter
|
|
49
|
-
extends OTLPExporterBase<ReadableLogRecord[]>
|
|
50
|
-
implements LogRecordExporter
|
|
51
|
-
{
|
|
52
|
-
/**
|
|
53
|
-
* @param httpClient - A Fusion `IHttpClient` used for outbound HTTP transport.
|
|
54
|
-
*/
|
|
55
|
-
constructor(httpClient: IHttpClient) {
|
|
56
|
-
super(
|
|
57
|
-
createOtlpNetworkExportDelegate(
|
|
58
|
-
getSharedConfigurationDefaults(),
|
|
59
|
-
JsonLogsSerializer,
|
|
60
|
-
new ExporterMetrics({
|
|
61
|
-
componentType: 'fusion_otlp_http_log_exporter',
|
|
62
|
-
metricsHelper: LogsExporterMetricsHelper,
|
|
63
|
-
url: undefined,
|
|
64
|
-
meterProvider: undefined,
|
|
65
|
-
responseAttributesFromError: () => ({}),
|
|
66
|
-
}),
|
|
67
|
-
new HttpClientExporterTransport(httpClient),
|
|
68
|
-
),
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import type { IExporterTransport, ExportResponse } from '@opentelemetry/otlp-exporter-base';
|
|
2
|
-
|
|
3
|
-
import type { IHttpClient } from '@equinor/fusion-framework-module-http';
|
|
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
|
-
*/
|
|
11
|
-
function isExportRetryable(statusCode: number): boolean {
|
|
12
|
-
// Status codes of when we should consider retrying.
|
|
13
|
-
// 429 Too Many Requests
|
|
14
|
-
// 502 Bad Gateway
|
|
15
|
-
// 503 Service Unavailable
|
|
16
|
-
// 504 Gateway Timeout
|
|
17
|
-
const retryCodes = [429, 502, 503, 504];
|
|
18
|
-
return retryCodes.includes(statusCode);
|
|
19
|
-
}
|
|
20
|
-
|
|
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
|
-
*/
|
|
27
|
-
function parseRetryAfterToMills(retryAfter?: string | undefined | null): number | undefined {
|
|
28
|
-
// No header present — nothing to compute
|
|
29
|
-
if (retryAfter == null) {
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const seconds = Number.parseInt(retryAfter, 10);
|
|
34
|
-
// Retry-After given as a plain integer number of seconds
|
|
35
|
-
if (Number.isInteger(seconds)) {
|
|
36
|
-
return seconds > 0 ? seconds * 1000 : -1;
|
|
37
|
-
}
|
|
38
|
-
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#directives
|
|
39
|
-
const delay = new Date(retryAfter).getTime() - Date.now();
|
|
40
|
-
|
|
41
|
-
// Only report a positive delay; a past date means retry is already due
|
|
42
|
-
if (delay >= 0) {
|
|
43
|
-
return delay;
|
|
44
|
-
}
|
|
45
|
-
return 0;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* OpenTelemetry exporter transport that posts serialised log records using a
|
|
50
|
-
* Fusion `IHttpClient`.
|
|
51
|
-
*
|
|
52
|
-
* @remarks
|
|
53
|
-
* Implements `IExporterTransport` from `@opentelemetry/otlp-exporter-base`.
|
|
54
|
-
* The transport honours the exporter’s timeout via `AbortController` and
|
|
55
|
-
* inspects response status codes to report retryable failures (429, 502–504).
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* ```ts
|
|
59
|
-
* const transport = new HttpClientExporterTransport(httpClient, '/v1/logs');
|
|
60
|
-
* ```
|
|
61
|
-
*/
|
|
62
|
-
export class HttpClientExporterTransport implements IExporterTransport {
|
|
63
|
-
/**
|
|
64
|
-
* @param httpClient - Fusion HTTP client for outgoing requests.
|
|
65
|
-
* @param path - URL path appended to the client’s base URL (default `'/v1/logs'`).
|
|
66
|
-
*/
|
|
67
|
-
constructor(
|
|
68
|
-
private httpClient: IHttpClient,
|
|
69
|
-
private path: string = '/v1/logs',
|
|
70
|
-
) {}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Sends serialised log record data to the configured endpoint.
|
|
74
|
-
*
|
|
75
|
-
* @param data - Serialised OTLP payload as a `Uint8Array`.
|
|
76
|
-
* @param timeoutMillis - Maximum time in milliseconds before the request is aborted.
|
|
77
|
-
* @returns An `ExportResponse` indicating success, retryable failure, or permanent failure.
|
|
78
|
-
*/
|
|
79
|
-
async send(data: Uint8Array, timeoutMillis: number): Promise<ExportResponse> {
|
|
80
|
-
const abortController = new AbortController();
|
|
81
|
-
const timeout = setTimeout(() => abortController.abort(), timeoutMillis);
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
const response = await this.httpClient.fetch(this.path, {
|
|
85
|
-
method: 'POST',
|
|
86
|
-
body: new Blob([data.slice().buffer], { type: 'application/json' }),
|
|
87
|
-
signal: abortController.signal,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// A 2xx response means the export succeeded outright
|
|
91
|
-
if (response.ok) {
|
|
92
|
-
return { status: 'success' };
|
|
93
|
-
} else if (isExportRetryable(response.status)) {
|
|
94
|
-
const retryAfter = response.headers.get('Retry-After');
|
|
95
|
-
const retryInMillis = parseRetryAfterToMills(retryAfter);
|
|
96
|
-
return { status: 'retryable', retryInMillis };
|
|
97
|
-
}
|
|
98
|
-
return {
|
|
99
|
-
status: 'failure',
|
|
100
|
-
error: new Error('Fetch request failed with non-retryable status'),
|
|
101
|
-
};
|
|
102
|
-
} catch (error) {
|
|
103
|
-
const err = error as Error;
|
|
104
|
-
// Distinguish a timeout (AbortController fired) from other fetch failures
|
|
105
|
-
if (err?.name === 'AbortError') {
|
|
106
|
-
return {
|
|
107
|
-
status: 'failure',
|
|
108
|
-
error: new Error('Fetch request timed out', { cause: err }),
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
status: 'failure',
|
|
113
|
-
error: new Error('Fetch request errored', { cause: err }),
|
|
114
|
-
};
|
|
115
|
-
} finally {
|
|
116
|
-
clearTimeout(timeout);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/** No-op; the HTTP client does not hold persistent connections. */
|
|
120
|
-
shutdown(): void {
|
|
121
|
-
// intentionally left empty, nothing to do.
|
|
122
|
-
}
|
|
123
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
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
|
-
*/
|
|
9
|
-
export { FusionOTLPLogExporter } from './fusionOTLPLogExporter/FusionOTLPLogExporter.js';
|
|
10
|
-
export { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { Subject, filter, type Subscription } from 'rxjs';
|
|
2
|
-
|
|
3
|
-
import type { IAnalyticsAdapter } from '../adapters/AnalyticsAdapter.interface.js';
|
|
4
|
-
import type { AnalyticsEvent } from '../types.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Selects which recorded events {@link MockAnalyticsAdapter.waitForAnalytic} or
|
|
8
|
-
* {@link MockAnalyticsAdapter.getAnalytics} act on.
|
|
9
|
-
*
|
|
10
|
-
* - `string` — matches `event.name` exactly.
|
|
11
|
-
* - `string[]` — matches if `event.name` is any of the given entries.
|
|
12
|
-
* - `(event) => boolean` — arbitrary predicate over the full event.
|
|
13
|
-
*/
|
|
14
|
-
export type AnalyticsEventMatcher<T extends AnalyticsEvent = AnalyticsEvent> =
|
|
15
|
-
| string
|
|
16
|
-
| string[]
|
|
17
|
-
| ((event: T) => boolean);
|
|
18
|
-
|
|
19
|
-
/** Options accepted by {@link MockAnalyticsAdapter.waitForAnalytic}. */
|
|
20
|
-
export interface WaitForAnalyticOptions {
|
|
21
|
-
/**
|
|
22
|
-
* Maximum time in milliseconds to wait for a matching event.
|
|
23
|
-
* When elapsed the returned promise rejects.
|
|
24
|
-
*/
|
|
25
|
-
timeout?: number;
|
|
26
|
-
/**
|
|
27
|
-
* AbortSignal that can cancel the wait early.
|
|
28
|
-
* When aborted the returned promise rejects with the signal's reason.
|
|
29
|
-
*/
|
|
30
|
-
signal?: AbortSignal;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* An {@link IAnalyticsAdapter} that records every tracked event in-memory instead
|
|
35
|
-
* of exporting it to a backend, for asserting on analytics in tests.
|
|
36
|
-
*
|
|
37
|
-
* @remarks
|
|
38
|
-
* Register it like any other adapter via {@link IAnalyticsConfigurator.setAdapter};
|
|
39
|
-
* it does not interfere with other adapters registered alongside it.
|
|
40
|
-
*
|
|
41
|
-
* @template T - Analytics event type, defaults to {@link AnalyticsEvent}.
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```ts
|
|
45
|
-
* import { MockAnalyticsAdapter } from '@equinor/fusion-framework-module-analytics/mock';
|
|
46
|
-
*
|
|
47
|
-
* const recorder = new MockAnalyticsAdapter();
|
|
48
|
-
* enableAnalytics(configurator, (builder) => {
|
|
49
|
-
* builder.setAdapter('mock', async () => recorder);
|
|
50
|
-
* });
|
|
51
|
-
*
|
|
52
|
-
* // ...later, in a test
|
|
53
|
-
* const event = await recorder.waitForAnalytic('button-click');
|
|
54
|
-
* expect(event.attributes?.section).toBe('header');
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export class MockAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent>
|
|
58
|
-
implements IAnalyticsAdapter<T>
|
|
59
|
-
{
|
|
60
|
-
#events: T[] = [];
|
|
61
|
-
#events$ = new Subject<T>();
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Records the event so it is visible to {@link getAnalytics} and any pending
|
|
65
|
-
* {@link waitForAnalytic} calls.
|
|
66
|
-
*
|
|
67
|
-
* @param event - The analytics event to record.
|
|
68
|
-
*/
|
|
69
|
-
registerAnalytic(event: T): void {
|
|
70
|
-
this.#events.push(event);
|
|
71
|
-
this.#events$.next(event);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Returns recorded events matching `matcher`, in dispatch order.
|
|
76
|
-
*
|
|
77
|
-
* @param matcher - Event name, array of names, or a predicate. Omit to get every recorded event.
|
|
78
|
-
* @returns Matching recorded events.
|
|
79
|
-
*/
|
|
80
|
-
getAnalytics(matcher?: AnalyticsEventMatcher<T>): T[] {
|
|
81
|
-
// No matcher: return every event recorded so far.
|
|
82
|
-
if (matcher === undefined) return [...this.#events];
|
|
83
|
-
// Narrow down to events accepted by the matcher.
|
|
84
|
-
return this.#events.filter((event) => this.#matches(event, matcher));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Waits for the next event matching `matcher`, resolving immediately if a
|
|
89
|
-
* matching event was already recorded.
|
|
90
|
-
*
|
|
91
|
-
* @param matcher - Event name, array of names, or a predicate.
|
|
92
|
-
* @param options - Optional timeout (ms) or AbortSignal.
|
|
93
|
-
* @returns A promise that resolves with the first matching event.
|
|
94
|
-
*/
|
|
95
|
-
waitForAnalytic(matcher: AnalyticsEventMatcher<T>, options?: WaitForAnalyticOptions): Promise<T> {
|
|
96
|
-
// Already recorded: resolve immediately rather than only watching future events.
|
|
97
|
-
const recorded = this.#events.find((event) => this.#matches(event, matcher));
|
|
98
|
-
// Already recorded: resolve immediately rather than only watching future events.
|
|
99
|
-
if (recorded) return Promise.resolve(recorded);
|
|
100
|
-
|
|
101
|
-
return new Promise<T>((resolve, reject) => {
|
|
102
|
-
const { timeout: ms, signal } = options ?? {};
|
|
103
|
-
|
|
104
|
-
// Fail fast without subscribing when the caller already aborted.
|
|
105
|
-
if (signal?.aborted) {
|
|
106
|
-
reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
111
|
-
// Declared before subscribing so `complete` can reach it even when the
|
|
112
|
-
// adapter is already disposed and fires synchronously during `subscribe`.
|
|
113
|
-
let sub: Subscription | undefined;
|
|
114
|
-
|
|
115
|
-
const cleanup = () => {
|
|
116
|
-
clearTimeout(timer);
|
|
117
|
-
sub?.unsubscribe();
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// Only forward events accepted by the matcher to the subscriber below.
|
|
121
|
-
sub = this.#events$.pipe(filter((event) => this.#matches(event, matcher))).subscribe({
|
|
122
|
-
next: (event) => {
|
|
123
|
-
cleanup();
|
|
124
|
-
resolve(event);
|
|
125
|
-
},
|
|
126
|
-
// A throwing predicate matcher surfaces here instead of hanging the promise forever.
|
|
127
|
-
error: (err) => {
|
|
128
|
-
cleanup();
|
|
129
|
-
reject(err);
|
|
130
|
-
},
|
|
131
|
-
complete: () => {
|
|
132
|
-
cleanup();
|
|
133
|
-
reject(new Error('MockAnalyticsAdapter disposed before a matching event was recorded'));
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Only arm a timeout when the caller opted in.
|
|
138
|
-
if (ms !== undefined) {
|
|
139
|
-
timer = setTimeout(() => {
|
|
140
|
-
cleanup();
|
|
141
|
-
reject(new Error(`waitForAnalytic timed out after ${ms}ms`));
|
|
142
|
-
}, ms);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Only wire abort handling when the caller passed a signal.
|
|
146
|
-
if (signal) {
|
|
147
|
-
signal.addEventListener(
|
|
148
|
-
'abort',
|
|
149
|
-
() => {
|
|
150
|
-
cleanup();
|
|
151
|
-
reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));
|
|
152
|
-
},
|
|
153
|
-
{ once: true },
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Tests whether `event` satisfies `matcher`.
|
|
161
|
-
*
|
|
162
|
-
* @param event - Event to test.
|
|
163
|
-
* @param matcher - Event name, array of names, or a predicate.
|
|
164
|
-
* @returns Whether `event` matches.
|
|
165
|
-
*/
|
|
166
|
-
#matches(event: T, matcher: AnalyticsEventMatcher<T>): boolean {
|
|
167
|
-
// String matcher: compare event name directly.
|
|
168
|
-
if (typeof matcher === 'string') return event.name === matcher;
|
|
169
|
-
// Array matcher: match against any of the given names.
|
|
170
|
-
if (Array.isArray(matcher)) return matcher.includes(event.name);
|
|
171
|
-
return matcher(event);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** Completes the internal event stream, rejecting any pending `waitForAnalytic` calls. */
|
|
175
|
-
[Symbol.dispose]() {
|
|
176
|
-
this.#events$.complete();
|
|
177
|
-
}
|
|
178
|
-
}
|
package/src/mock/index.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mock analytics adapter for tests: records tracked events in-memory instead
|
|
3
|
-
* of exporting them to a backend.
|
|
4
|
-
*
|
|
5
|
-
* @remarks
|
|
6
|
-
* Register it like any other {@link IAnalyticsAdapter} via
|
|
7
|
-
* {@link IAnalyticsConfigurator.setAdapter} — it observes tracked events
|
|
8
|
-
* alongside real adapters without affecting their delivery.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* import { MockAnalyticsAdapter } from '@equinor/fusion-framework-module-analytics/mock';
|
|
13
|
-
*
|
|
14
|
-
* const recorder = new MockAnalyticsAdapter();
|
|
15
|
-
* enableAnalytics(configurator, (builder) => {
|
|
16
|
-
* builder.setAdapter('mock', async () => recorder);
|
|
17
|
-
* });
|
|
18
|
-
*
|
|
19
|
-
* const event = await recorder.waitForAnalytic('button-click');
|
|
20
|
-
* expect(event.attributes?.section).toBe('header');
|
|
21
|
-
* ```
|
|
22
|
-
*
|
|
23
|
-
* @packageDocumentation
|
|
24
|
-
*/
|
|
25
|
-
export {
|
|
26
|
-
MockAnalyticsAdapter,
|
|
27
|
-
type AnalyticsEventMatcher,
|
|
28
|
-
type WaitForAnalyticOptions,
|
|
29
|
-
} from './MockAnalyticsAdapter.js';
|
package/src/module.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import type { Module } from '@equinor/fusion-framework-module';
|
|
2
|
-
import type { IAnalyticsConfigurator } from './AnalyticsConfigurator.interface.js';
|
|
3
|
-
import type { IAnalyticsProvider } from './AnalyticsProvider.interface.js';
|
|
4
|
-
import { AnalyticsConfigurator } from './AnalyticsConfigurator.js';
|
|
5
|
-
import { AnalyticsProvider } from './AnalyticsProvider.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Represents the Analytics module type within the Fusion Framework module system.
|
|
9
|
-
*
|
|
10
|
-
* @remarks
|
|
11
|
-
* This type defines a module named `analytics` that integrates with the framework's
|
|
12
|
-
* module system. It specifies the provider and configurator interfaces for analytics
|
|
13
|
-
* functionality, and declares a self-dependency so that child modules can inherit
|
|
14
|
-
* analytics from a parent scope.
|
|
15
|
-
*
|
|
16
|
-
* @see {@link IAnalyticsProvider} for the runtime provider interface.
|
|
17
|
-
* @see {@link IAnalyticsConfigurator} for configuration-time setup.
|
|
18
|
-
*/
|
|
19
|
-
export type AnalyticsModule = Module<
|
|
20
|
-
'analytics',
|
|
21
|
-
IAnalyticsProvider,
|
|
22
|
-
IAnalyticsConfigurator,
|
|
23
|
-
[AnalyticsModule]
|
|
24
|
-
>;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Analytics module definition for the Fusion Framework.
|
|
28
|
-
*
|
|
29
|
-
* @remarks
|
|
30
|
-
* This module provides analytics capabilities by configuring and initializing an
|
|
31
|
-
* {@link AnalyticsProvider}. Register it with {@link enableAnalytics} or add it
|
|
32
|
-
* directly to a module configurator.
|
|
33
|
-
*
|
|
34
|
-
* - `name` — `'analytics'`
|
|
35
|
-
* - `configure()` — creates a new {@link AnalyticsConfigurator} instance.
|
|
36
|
-
* - `initialize(args)` — resolves the configuration, creates an
|
|
37
|
-
* {@link AnalyticsProvider}, calls {@link AnalyticsProvider.initialize},
|
|
38
|
-
* and returns the ready-to-use provider.
|
|
39
|
-
*/
|
|
40
|
-
export const module = {
|
|
41
|
-
name: 'analytics',
|
|
42
|
-
configure: () => new AnalyticsConfigurator(),
|
|
43
|
-
initialize: async (args): Promise<IAnalyticsProvider> => {
|
|
44
|
-
const config = await (args.config as AnalyticsConfigurator).createConfigAsync(args);
|
|
45
|
-
|
|
46
|
-
const provider = new AnalyticsProvider(config);
|
|
47
|
-
await provider.initialize();
|
|
48
|
-
|
|
49
|
-
return provider;
|
|
50
|
-
},
|
|
51
|
-
} satisfies AnalyticsModule;
|
|
52
|
-
|
|
53
|
-
export default module;
|
|
54
|
-
|
|
55
|
-
declare module '@equinor/fusion-framework-module' {
|
|
56
|
-
interface Modules {
|
|
57
|
-
analytics: AnalyticsModule;
|
|
58
|
-
}
|
|
59
|
-
}
|