@equinor/fusion-framework-module-analytics 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +248 -0
- package/dist/esm/AnalyticsConfigurator.interface.js +2 -0
- package/dist/esm/AnalyticsConfigurator.interface.js.map +1 -0
- package/dist/esm/AnalyticsConfigurator.js +61 -0
- package/dist/esm/AnalyticsConfigurator.js.map +1 -0
- package/dist/esm/AnalyticsProvider.interface.js +2 -0
- package/dist/esm/AnalyticsProvider.interface.js.map +1 -0
- package/dist/esm/AnalyticsProvider.js +92 -0
- package/dist/esm/AnalyticsProvider.js.map +1 -0
- package/dist/esm/adapters/AnalyticsAdapter.interface.js +2 -0
- package/dist/esm/adapters/AnalyticsAdapter.interface.js.map +1 -0
- package/dist/esm/adapters/ConsoleAnalyticsAdapter.js +12 -0
- package/dist/esm/adapters/ConsoleAnalyticsAdapter.js.map +1 -0
- package/dist/esm/adapters/FusionAnalyticsAdapter.js +42 -0
- package/dist/esm/adapters/FusionAnalyticsAdapter.js.map +1 -0
- package/dist/esm/adapters/index.js +3 -0
- package/dist/esm/adapters/index.js.map +1 -0
- package/dist/esm/collectors/AnalyticsCollector.interface.js +2 -0
- package/dist/esm/collectors/AnalyticsCollector.interface.js.map +1 -0
- package/dist/esm/collectors/AppLoadedCollector.js +42 -0
- package/dist/esm/collectors/AppLoadedCollector.js.map +1 -0
- package/dist/esm/collectors/AppSelectedCollector.js +32 -0
- package/dist/esm/collectors/AppSelectedCollector.js.map +1 -0
- package/dist/esm/collectors/BaseCollector.js +40 -0
- package/dist/esm/collectors/BaseCollector.js.map +1 -0
- package/dist/esm/collectors/ContextSelectedCollector.js +36 -0
- package/dist/esm/collectors/ContextSelectedCollector.js.map +1 -0
- package/dist/esm/collectors/index.js +4 -0
- package/dist/esm/collectors/index.js.map +1 -0
- package/dist/esm/collectors/utils/extractAppMetadata.js +37 -0
- package/dist/esm/collectors/utils/extractAppMetadata.js.map +1 -0
- package/dist/esm/collectors/utils/extractContextMetadata.js +23 -0
- package/dist/esm/collectors/utils/extractContextMetadata.js.map +1 -0
- package/dist/esm/enable-analytics.js +20 -0
- package/dist/esm/enable-analytics.js.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.js +22 -0
- package/dist/esm/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.js.map +1 -0
- package/dist/esm/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.js +84 -0
- package/dist/esm/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.js.map +1 -0
- package/dist/esm/logExporters/index.js +3 -0
- package/dist/esm/logExporters/index.js.map +1 -0
- package/dist/esm/module.js +28 -0
- package/dist/esm/module.js.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/version.js +3 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/AnalyticsConfigurator.d.ts +39 -0
- package/dist/types/AnalyticsConfigurator.interface.d.ts +39 -0
- package/dist/types/AnalyticsProvider.d.ts +45 -0
- package/dist/types/AnalyticsProvider.interface.d.ts +20 -0
- package/dist/types/adapters/AnalyticsAdapter.interface.d.ts +21 -0
- package/dist/types/adapters/ConsoleAnalyticsAdapter.d.ts +9 -0
- package/dist/types/adapters/FusionAnalyticsAdapter.d.ts +19 -0
- package/dist/types/adapters/index.d.ts +3 -0
- package/dist/types/collectors/AnalyticsCollector.interface.d.ts +16 -0
- package/dist/types/collectors/AppLoadedCollector.d.ts +23 -0
- package/dist/types/collectors/AppSelectedCollector.d.ts +21 -0
- package/dist/types/collectors/BaseCollector.d.ts +27 -0
- package/dist/types/collectors/ContextSelectedCollector.d.ts +21 -0
- package/dist/types/collectors/index.d.ts +4 -0
- package/dist/types/collectors/utils/extractAppMetadata.d.ts +17 -0
- package/dist/types/collectors/utils/extractContextMetadata.d.ts +11 -0
- package/dist/types/enable-analytics.d.ts +11 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.d.ts +11 -0
- package/dist/types/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.d.ts +12 -0
- package/dist/types/logExporters/index.d.ts +2 -0
- package/dist/types/module.d.ts +44 -0
- package/dist/types/types.d.ts +22 -0
- package/dist/types/version.d.ts +1 -0
- package/package.json +72 -0
- package/src/AnalyticsConfigurator.interface.ts +48 -0
- package/src/AnalyticsConfigurator.ts +116 -0
- package/src/AnalyticsProvider.interface.ts +22 -0
- package/src/AnalyticsProvider.ts +118 -0
- package/src/adapters/AnalyticsAdapter.interface.ts +23 -0
- package/src/adapters/ConsoleAnalyticsAdapter.ts +17 -0
- package/src/adapters/FusionAnalyticsAdapter.ts +61 -0
- package/src/adapters/index.ts +3 -0
- package/src/collectors/AnalyticsCollector.interface.ts +18 -0
- package/src/collectors/AppLoadedCollector.ts +74 -0
- package/src/collectors/AppSelectedCollector.ts +53 -0
- package/src/collectors/BaseCollector.ts +65 -0
- package/src/collectors/ContextSelectedCollector.ts +56 -0
- package/src/collectors/index.ts +4 -0
- package/src/collectors/utils/extractAppMetadata.ts +45 -0
- package/src/collectors/utils/extractContextMetadata.ts +27 -0
- package/src/enable-analytics.ts +27 -0
- package/src/index.ts +11 -0
- package/src/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.ts +41 -0
- package/src/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.ts +89 -0
- package/src/logExporters/index.ts +2 -0
- package/src/module.ts +59 -0
- package/src/types.ts +29 -0
- package/src/version.ts +2 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ContextItem } from '@equinor/fusion-framework-module-context';
|
|
3
|
+
|
|
4
|
+
// Schema representing an object with data points of a Fusion context.
|
|
5
|
+
// Used to parse an object with context data.
|
|
6
|
+
export const contextSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
id: z.string(),
|
|
9
|
+
type: z.string(),
|
|
10
|
+
title: z.string().optional(),
|
|
11
|
+
externalId: z.string().optional(),
|
|
12
|
+
source: z.string().optional(),
|
|
13
|
+
})
|
|
14
|
+
.optional()
|
|
15
|
+
.nullable();
|
|
16
|
+
|
|
17
|
+
export type ContextItemType = z.infer<typeof contextSchema>;
|
|
18
|
+
|
|
19
|
+
export const extractContextMetadata = (context: ContextItem): z.input<typeof contextSchema> => {
|
|
20
|
+
return {
|
|
21
|
+
id: context.id,
|
|
22
|
+
externalId: context.externalId,
|
|
23
|
+
title: context.title,
|
|
24
|
+
type: context.type.id,
|
|
25
|
+
source: context.source,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
type AnalyticsBuilderCallback = (builder: IAnalyticsConfigurator) => void | Promise<void>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Enables analytics for agiven module configurator.
|
|
10
|
+
*
|
|
11
|
+
* @param configurator - The module configurator instance to which analytics should be attached.
|
|
12
|
+
* @param callback - An optional callback to further configure the analytics builder. Can be synchronous or asynchronous.
|
|
13
|
+
*/
|
|
14
|
+
export const enableAnalytics = (
|
|
15
|
+
// biome-ignore lint/suspicious/noExplicitAny: must be any to support all module types
|
|
16
|
+
configurator: IModulesConfigurator<any, any>,
|
|
17
|
+
callback?: AnalyticsBuilderCallback,
|
|
18
|
+
): void => {
|
|
19
|
+
configurator.addConfig({
|
|
20
|
+
module,
|
|
21
|
+
configure: async (builder) => {
|
|
22
|
+
if (callback) {
|
|
23
|
+
await Promise.resolve(callback(builder));
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { module as analyticsModule, AnalyticsModule } from './module.js';
|
|
2
|
+
|
|
3
|
+
export { AnalyticsEvent } from './types.js';
|
|
4
|
+
|
|
5
|
+
export { AnalyticsProvider } from './AnalyticsProvider.js';
|
|
6
|
+
export { IAnalyticsProvider } from './AnalyticsProvider.interface.js';
|
|
7
|
+
|
|
8
|
+
export { AnalyticsConfig, IAnalyticsConfigurator } from './AnalyticsConfigurator.interface.js';
|
|
9
|
+
export { AnalyticsConfigurator } from './AnalyticsConfigurator.js';
|
|
10
|
+
|
|
11
|
+
export { enableAnalytics } from './enable-analytics.js';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { LogRecordExporter, ReadableLogRecord } from '@opentelemetry/sdk-logs';
|
|
2
|
+
|
|
3
|
+
import { JsonLogsSerializer } from '@opentelemetry/otlp-transformer';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createOtlpNetworkExportDelegate,
|
|
7
|
+
OTLPExporterBase,
|
|
8
|
+
type OtlpSharedConfiguration,
|
|
9
|
+
} from '@opentelemetry/otlp-exporter-base';
|
|
10
|
+
|
|
11
|
+
import type { IHttpClient } from '@equinor/fusion-framework-module-http';
|
|
12
|
+
import { HttpClientExporterTransport } from './HttpClientExporterTransport.js';
|
|
13
|
+
|
|
14
|
+
// The shared default configuration needed for the OTLPNetworkExportDelegate
|
|
15
|
+
function getSharedConfigurationDefaults(): OtlpSharedConfiguration {
|
|
16
|
+
return {
|
|
17
|
+
timeoutMillis: 10000,
|
|
18
|
+
concurrencyLimit: 30,
|
|
19
|
+
compression: 'none',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A log exporter extending OTLPExporterBase but substitute the normal transport
|
|
25
|
+
* with a HttpClientExporterTransport. This will use the provided httpClient to
|
|
26
|
+
* emit the event.
|
|
27
|
+
*/
|
|
28
|
+
export class FusionOTLPLogExporter
|
|
29
|
+
extends OTLPExporterBase<ReadableLogRecord[]>
|
|
30
|
+
implements LogRecordExporter
|
|
31
|
+
{
|
|
32
|
+
constructor(httpClient: IHttpClient) {
|
|
33
|
+
super(
|
|
34
|
+
createOtlpNetworkExportDelegate(
|
|
35
|
+
getSharedConfigurationDefaults(),
|
|
36
|
+
JsonLogsSerializer,
|
|
37
|
+
new HttpClientExporterTransport(httpClient),
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { IExporterTransport, ExportResponse } from '@opentelemetry/otlp-exporter-base';
|
|
2
|
+
|
|
3
|
+
import type { IHttpClient } from '@equinor/fusion-framework-module-http';
|
|
4
|
+
|
|
5
|
+
function isExportRetryable(statusCode: number): boolean {
|
|
6
|
+
// Status codes of when we should consider retrying.
|
|
7
|
+
// 429 Too Many Requests
|
|
8
|
+
// 502 Bad Gateway
|
|
9
|
+
// 503 Service Unavailable
|
|
10
|
+
// 504 Gateway Timeout
|
|
11
|
+
const retryCodes = [429, 502, 503, 504];
|
|
12
|
+
return retryCodes.includes(statusCode);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Parse the `Retry-After` header and return when the service will allow a retry.
|
|
16
|
+
function parseRetryAfterToMills(retryAfter?: string | undefined | null): number | undefined {
|
|
17
|
+
if (retryAfter == null) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const seconds = Number.parseInt(retryAfter, 10);
|
|
22
|
+
if (Number.isInteger(seconds)) {
|
|
23
|
+
return seconds > 0 ? seconds * 1000 : -1;
|
|
24
|
+
}
|
|
25
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#directives
|
|
26
|
+
const delay = new Date(retryAfter).getTime() - Date.now();
|
|
27
|
+
|
|
28
|
+
if (delay >= 0) {
|
|
29
|
+
return delay;
|
|
30
|
+
}
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A Exporter Transport to POST events to provided path using the provided httpClient
|
|
36
|
+
*/
|
|
37
|
+
export class HttpClientExporterTransport implements IExporterTransport {
|
|
38
|
+
constructor(
|
|
39
|
+
private httpClient: IHttpClient,
|
|
40
|
+
private path: string = '/v1/logs',
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
// Will send data with the httpClient.
|
|
44
|
+
// If the service responds with a non 2** statusCode, we check if it is
|
|
45
|
+
// retryable.
|
|
46
|
+
// The timeoutMillis determines when to abort if the service has not yet
|
|
47
|
+
// responded.
|
|
48
|
+
async send(data: Uint8Array, timeoutMillis: number): Promise<ExportResponse> {
|
|
49
|
+
const abortController = new AbortController();
|
|
50
|
+
const timeout = setTimeout(() => abortController.abort(), timeoutMillis);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const response = await this.httpClient.fetch(this.path, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
body: new Blob([data.slice().buffer], { type: 'application/json' }),
|
|
56
|
+
signal: abortController.signal,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (response.ok) {
|
|
60
|
+
return { status: 'success' };
|
|
61
|
+
} else if (isExportRetryable(response.status)) {
|
|
62
|
+
const retryAfter = response.headers.get('Retry-After');
|
|
63
|
+
const retryInMillis = parseRetryAfterToMills(retryAfter);
|
|
64
|
+
return { status: 'retryable', retryInMillis };
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
status: 'failure',
|
|
68
|
+
error: new Error('Fetch request failed with non-retryable status'),
|
|
69
|
+
};
|
|
70
|
+
} catch (error) {
|
|
71
|
+
const err = error as Error;
|
|
72
|
+
if (err?.name === 'AbortError') {
|
|
73
|
+
return {
|
|
74
|
+
status: 'failure',
|
|
75
|
+
error: new Error('Fetch request timed out', { cause: err }),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
status: 'failure',
|
|
80
|
+
error: new Error('Fetch request errored', { cause: err }),
|
|
81
|
+
};
|
|
82
|
+
} finally {
|
|
83
|
+
clearTimeout(timeout);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
shutdown(): void {
|
|
87
|
+
// intentionally left empty, nothing to do.
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/module.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
* Representes the Analytics module within the framework.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* This type defines a module names `analytics` that integrates with the framwork's module system.
|
|
12
|
+
* It specifies the provider and configurator interfaces for analytics functionality, and declares
|
|
13
|
+
* its dependencies on itself (`AnalyticsModule`).
|
|
14
|
+
*
|
|
15
|
+
* @typeParam IAnalyticsProvider - The interface for the analytics provider implementation.
|
|
16
|
+
* @typeParam IAnalyticsConfigurator - The interface for configuring analytics behavior.
|
|
17
|
+
* @typeParam AnalyticsModule - Self-reference to allow for recursive or hierarchical module composition.
|
|
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 a analytics provider.
|
|
31
|
+
*
|
|
32
|
+
* @type {AnalyticsModule}
|
|
33
|
+
*
|
|
34
|
+
* @property {string} name - The name of the module ('analytics').
|
|
35
|
+
* @property {() => AnalyticsConfigurator} configure - Factory function to create a new AnalyticsConfigurator instance.
|
|
36
|
+
* @property {(args) => Promise<IAnalyticsProvider>} initialize - Asynchronous initializer that creates and returns a AnalyticsProvider.
|
|
37
|
+
* - @param args - Initialization arguments, including configuration and module dependencies.
|
|
38
|
+
* - @returns A promise that resolves to an instance of IAnalyticsProvider.
|
|
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
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type AnyValueScalar = string | number | boolean;
|
|
2
|
+
export type AnyValueArray = Array<AnyValue>;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AnyValueMap is a map from string to AnyValue (attribute value or a nested map)
|
|
6
|
+
*/
|
|
7
|
+
export interface AnyValueMap {
|
|
8
|
+
[attributeKey: string]: AnyValue;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* AnyValue can be one of the following:
|
|
13
|
+
* - a scalar value
|
|
14
|
+
* - a byte array
|
|
15
|
+
* - array of any value
|
|
16
|
+
* - map from string to any value
|
|
17
|
+
* - empty value
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export type AnyValue = AnyValueScalar | Uint8Array | AnyValueArray | AnyValueMap | null | undefined;
|
|
21
|
+
|
|
22
|
+
export type AnalyticsEvent<
|
|
23
|
+
TValue extends AnyValue = AnyValue,
|
|
24
|
+
TAttr extends AnyValueMap = AnyValueMap,
|
|
25
|
+
> = {
|
|
26
|
+
name: string;
|
|
27
|
+
value: TValue;
|
|
28
|
+
attributes?: TAttr;
|
|
29
|
+
};
|
package/src/version.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "dist/esm",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"declarationDir": "./dist/types"
|
|
9
|
+
},
|
|
10
|
+
"references": [
|
|
11
|
+
{
|
|
12
|
+
"path": "../module"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"path": "../event"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "../app"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "../context"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"path": "../http"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"include": ["src/**/*"],
|
|
28
|
+
"exclude": ["node_modules", "lib"]
|
|
29
|
+
}
|