Skip to content

Commit 69d9f98

Browse files
committed
feat(analytics): add analytics module
Ref AB#65764 Ref AB#65745 Ref #3611
1 parent 3522425 commit 69d9f98

34 files changed

+1898
-6
lines changed

packages/app/src/configure-modules.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import { AppConfigurator } from './AppConfigurator';
99
import type { AppModulesInstance, AppModuleInitiator, AppEnv } from './types';
1010

1111
/**
12-
*
12+
*
1313
* Creates a callback for initializing configuration of application modules
14-
*
14+
*
1515
* @example
1616
```ts
1717
const initialize = configureModules((configurator, args) => {
@@ -22,9 +22,9 @@ import type { AppModulesInstance, AppModuleInitiator, AppEnv } from './types';
2222
* @template TModules Addition modules
2323
* @template TRef usually undefined, optional references
2424
* @template TEnv environment object for configuring modules
25-
*
25+
*
2626
* @param cb configuration callback
27-
*
27+
*
2828
* @returns initialize function, executes configurator
2929
*/
3030
export const configureModules =
@@ -86,7 +86,7 @@ export const configureModules =
8686
// TODO - remove check after fusion-cli is updated (app module is not enabled in fusion-cli)
8787
if (args.env.manifest?.appKey) {
8888
modules.event.dispatchEvent('onAppModulesLoaded', {
89-
detail: { appKey: args.env.manifest.appKey, modules },
89+
detail: { appKey: args.env.manifest.appKey, manifest: args.env.manifest, modules },
9090
});
9191
}
9292
return modules;

packages/cli/src/lib/dev-server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export { processServices } from '@equinor/fusion-framework-dev-server';
12
export {
23
loadDevServerConfig,
34
defineDevServerConfig,
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
# Analytics Module
2+
3+
The Fusion Framework Analytics Module provides an unified way to track analytics.
4+
It offers a consistent API for logging analytics data while supporting multiple
5+
adapters and collectors.
6+
7+
When a collector emits a event, it will be sent to all adapters.
8+
9+
## Configuration
10+
11+
To configure the analytics module, you need to provide a function that specifies
12+
the adapters and collectors you want to use. The configuration can be done in
13+
your portal entry file or wherever you initialize the Fusion Framework.
14+
15+
```typescript
16+
import { enableAnalytics } from '@equinor/fusion-framework-module-analytics';
17+
18+
const configure = (configurator: IModulesConfigurator<any, any>) => {
19+
enableAnalytics(configurator, (builder) => {
20+
// configure the analytics module
21+
22+
builder.setAdapter('console', async () => {
23+
return new ConsoleAnalyticsAdapter();
24+
});
25+
26+
builder.setCollector('context-selected', async (args) => {
27+
const contextProvider = await args.requireInstance('context');
28+
return new ContextSelectedCollector(contextProvider);
29+
});
30+
});
31+
}
32+
```
33+
34+
## Initialization
35+
36+
The analytics module supports asynchronous initialization of adapters and
37+
collectors. The provider exposes an `initialize()` method that should be called
38+
to set up all configured adapters and collectors:
39+
40+
```typescript
41+
// Get the analytics provider from modules
42+
const provider = modules.analytics;
43+
44+
// Initialize adapters and collectors (required before use)
45+
await provider.initialize();
46+
47+
// Check if provider is initialized
48+
if (provider.initialized) {
49+
// Provider is ready for use
50+
}
51+
```
52+
53+
> [!NOTE]
54+
> The analytics module will automatically initialize when used within the
55+
> Fusion Framework module system. Manual initialization is only required when
56+
> accessing the provider directly.
57+
58+
## Adapters
59+
60+
Adapters are responsible for processing and sending telemetry data to their
61+
respective destinations. All adapters support asynchronous initialization and
62+
will automatically be initialized when the analytics provider initializes.
63+
64+
### Using `setAdapter`
65+
66+
The `AnalyticsConfigurator` provides a method for adding adapters: `setAdapter`
67+
for pre-instantiated adapters.
68+
69+
#### Simple `setAdapter` Example
70+
71+
```typescript
72+
import { enableAnalytics } from '@equinor/fusion-framework-module-analytics';
73+
import { ConsoleAnalyticsAdapter } from '@equinor/fusion-framework-module-analytics/adapters';
74+
75+
const configure = (configurator: IModulesConfigurator<any, any>) => {
76+
enableAnalytics(configurator, (builder) => {
77+
builder.setAdapter('console', async () => {
78+
return new ConsoleAnalyticsAdapter();
79+
});
80+
});
81+
}
82+
```
83+
84+
### Console Adapter
85+
86+
The `ConsoleAnalyticsAdapter` provides a simple way to log analytics data to the
87+
console, useful for development and debugging.
88+
89+
#### Installation
90+
91+
The Console Adapter is included with the analytics module and doesn't require
92+
additional installation.
93+
94+
#### Configuration
95+
96+
The Console Adapter does not require any configuration.
97+
98+
### Fusion Analytics Adapter
99+
100+
The `FusionAnalyticsAdapter` provides a way to log analytics data to a service
101+
using the OpenTelemetry Logs pattern.
102+
103+
#### Installation
104+
105+
The Fusion Analytics Adapter is included with the analytics module and doesn't
106+
require additional installation.
107+
108+
#### Configuration
109+
110+
The Fusion Analytics Adapter support the following configuration options:
111+
112+
- `portalId`: The portal Id to be included in log records.
113+
114+
- `logExporter`: The exporter to use. We bundle `OTLPLogExporter` and `FusionOTLPLogExporter` - see below.
115+
116+
##### Example configuration
117+
118+
```typescript
119+
// OTLPLogExporter
120+
import { OTLPLogExporter } from '@equinor/fusion-framework-module-analytics/logExporters';
121+
122+
builder.setAdapter('fusion-log', async () => {
123+
const logExporter = new OTLPLogExporter({
124+
url: 'URL_TO_POST_TO',
125+
headers: {
126+
'Content-Type': 'application/json',
127+
},
128+
});
129+
130+
return new FusionAnalyticsAdapter({
131+
portalId: 'PORTAL_ID',
132+
logExporter,
133+
});
134+
});
135+
136+
// FusionOTLPLogExporter with httpClient
137+
import { FusionOTLPLogExporter } from '@equinor/fusion-framework-module-analytics/logExporters';
138+
139+
builder.setAdapter('fusion', async (args) => {
140+
if (args.hasModule('serviceDiscovery')) {
141+
const serviceDiscovery = await args.requireInstance<ServiceDiscoveryProvider>(
142+
'serviceDiscovery'
143+
);
144+
const httpClient = await serviceDiscovery.createClient('SERVICE_DISCOVERY_NAME');
145+
146+
const logExporter = new FusionOTLPLogExporter(httpClient);
147+
148+
return new FusionAnalyticsAdapter({
149+
portalId: 'PORTAL_ID',
150+
logExporter,
151+
});
152+
}
153+
console.error('Could not instanziate monitor http client from service-discovery');
154+
});
155+
```
156+
157+
### Creating Custom Adapters
158+
159+
You can create custom analytics adapters by implementing the `IAnalyticsAdapter`
160+
interface and add it in configuration.
161+
162+
## Collectors
163+
164+
Collectors are responsible for sending events that adapters can pick up. All
165+
collectors support asynchronous initialization and will automatically be
166+
initialized when the analytics provider initializes.
167+
168+
### Using `setCollector`
169+
170+
The `AnalyticsConfigurator` provides a method for adding collectors:
171+
`setCollector` for pre-instantiated collectors.
172+
173+
### Simple `setCollector` Example
174+
175+
```typescript
176+
import { enableAnalytics } from '@equinor/fusion-framework-module-analytics';
177+
import { ContextSelectedCollector } from '@equinor/fusion-framework-module-analytics/collectors';
178+
179+
const configure = (configurator: IModulesConfigurator<any, any>) => {
180+
enableAnalytics(configurator, (builder) => {
181+
builder.setCollector('context-selected', async (args) => {
182+
const contextProvider = await args.requireInstance('context');
183+
return new ContextSelectedCollector(contextProvider);
184+
});
185+
});
186+
}
187+
```
188+
189+
### Context Selected Collector
190+
191+
The `ContextSelectedCollector` listens for selection in the context module and emits
192+
the new and optional previous context.
193+
194+
#### Installation
195+
196+
The Context Selected Collector is included with the analytics module and doesn't
197+
require additional installation.
198+
199+
#### Configuration
200+
201+
The Context Selected Collector needs the context provider.
202+
203+
##### Example configuration
204+
205+
```typescript
206+
import { enableAnalytics } from '@equinor/fusion-framework-module-analytics';
207+
import { ContextSelectedCollector } from '@equinor/fusion-framework-module-analytics/collectors';
208+
209+
const configure = (configurator: IModulesConfigurator<any, any>) => {
210+
enableAnalytics(configurator, (builder) => {
211+
builder.setCollector('context-selected', async (args) => {
212+
const contextProvider = await args.requireInstance('context');
213+
return new ContextSelectedCollector(contextProvider);
214+
});
215+
});
216+
}
217+
```
218+
219+
### Creating Custom Collectors
220+
221+
You can create custom analytics collector by extending the `BaseCollector` class,
222+
or implement the `IAnalyticsCollector` interface and add it in configuration.
223+
224+
#### Example Custom Collector
225+
226+
```typescript
227+
import { type AnalyticsEvent, enableAnalytics } from '@equinor/fusion-framework-module-analytics';
228+
229+
const configure = (configurator: IModulesConfigurator<any, any>) => {
230+
enableAnalytics(configurator, (builder) => {
231+
builder.setCollector('click-test', async () => {
232+
const subject = new Subject<AnalyticsEvent>();
233+
window.addEventListener('click', (e) => {
234+
subject.next({
235+
name: 'window-clicker',
236+
value: 42,
237+
});
238+
});
239+
240+
return {
241+
subscribe: (subscriber) => {
242+
return subject.subscribe(subscriber);
243+
},
244+
};
245+
});
246+
});
247+
}
248+
```
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"name": "@equinor/fusion-framework-module-analytics",
3+
"version": "0.0.0",
4+
"description": "Fusion module for collecting and exporting application analytics using OpenTelemetry standards",
5+
"main": "dist/esm/index.js",
6+
"type": "module",
7+
"exports": {
8+
".": {
9+
"import": "./dist/esm/index.js",
10+
"types": "./dist/types/index.d.ts"
11+
},
12+
"./adapters": {
13+
"import": "./dist/esm/adapters/index.js",
14+
"types": "./dist/types/adapters/index.d.ts"
15+
},
16+
"./collectors": {
17+
"import": "./dist/esm/collectors/index.js",
18+
"types": "./dist/types/collectors/index.d.ts"
19+
},
20+
"./logExporters": {
21+
"import": "./dist/esm/logExporters/index.js",
22+
"types": "./dist/types/logExporters/index.d.ts"
23+
}
24+
},
25+
"types": "dist/types/index.d.ts",
26+
"scripts": {
27+
"build": "tsc -b",
28+
"prepack": "pnpm build",
29+
"test": "vitest"
30+
},
31+
"keywords": [
32+
"analytics",
33+
"fusion",
34+
"utility"
35+
],
36+
"author": "",
37+
"license": "ISC",
38+
"publishConfig": {
39+
"access": "public"
40+
},
41+
"repository": {
42+
"type": "git",
43+
"url": "git+https://github.com/equinor/fusion-framework.git",
44+
"directory": "packages/modules/analytics"
45+
},
46+
"dependencies": {
47+
"@equinor/fusion-framework-module": "workspace:^",
48+
"@equinor/fusion-framework-module-app": "workspace:^",
49+
"@equinor/fusion-framework-module-context": "workspace:^",
50+
"@equinor/fusion-framework-module-event": "workspace:^",
51+
"@equinor/fusion-framework-react": "workspace:^",
52+
"@opentelemetry/api-logs": "^0.207.0",
53+
"@opentelemetry/exporter-logs-otlp-http": "^0.207.0",
54+
"@opentelemetry/otlp-exporter-base": "^0.207.0",
55+
"@opentelemetry/otlp-transformer": "^0.208.0",
56+
"@opentelemetry/resources": "^2.2.0",
57+
"@opentelemetry/sdk-logs": "^0.207.0",
58+
"deepmerge": "^4.3.1",
59+
"rxjs": "^7.8.1",
60+
"uuid": "^13.0.0",
61+
"zod": "^4.1.11"
62+
},
63+
"devDependencies": {
64+
"@equinor/fusion-observable": "workspace:^",
65+
"typescript": "^5.8.2",
66+
"vitest": "^3.2.4"
67+
},
68+
"peerDependencies": {
69+
"@equinor/fusion-framework-module": "workspace:^",
70+
"@equinor/fusion-framework-module-event": "workspace:^",
71+
"@equinor/fusion-observable": "workspace:^"
72+
}
73+
}

0 commit comments

Comments
 (0)