Initial implementation of an extension-based default analytics API
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
---
|
||||
|
||||
Plugins should now use the new `AnalyticsBlueprint` to define and provide concrete analytics implementations. For example:
|
||||
|
||||
```ts
|
||||
import { AnalyticsBlueprint } from '@backstage/frontend-plugin-api';
|
||||
|
||||
const AcmeAnalytics = AnalyticsBlueprint.make({
|
||||
name: 'acme-analytics',
|
||||
params: define =>
|
||||
define({
|
||||
deps: { config: configApiRef },
|
||||
factory: ({ config }) => AcmeAnalyticsImpl.fromConfig(config),
|
||||
}),
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-app': patch
|
||||
---
|
||||
|
||||
The default implementation of the Analytics API now collects and instantiates analytics implementations exposed via `AnalyticsBlueprint` extensions. If no such extensions are discovered, the API continues to do nothing with analytics events fired within Backstage. If multiple such extensions are discovered, every discovered implementation automatically receives analytics events.
|
||||
@@ -101,6 +101,24 @@ export const apis: AnyApiFactory[] = [
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
// Or, when building for the new frontend system:
|
||||
import { AnalyticsBlueprint } from '@backstage/frontend-plugin-api';
|
||||
|
||||
export const acmeAnalyticsImplementation = AnalyticsBlueprint.make({
|
||||
name: 'acme',
|
||||
params: define =>
|
||||
define({
|
||||
deps: {},
|
||||
factory() {
|
||||
return {
|
||||
captureEvent: event => {
|
||||
window._AcmeAnalyticsQ.push(event);
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
In reality, you would likely want to encapsulate instantiation logic and pull
|
||||
@@ -140,6 +158,18 @@ export const apis: AnyApiFactory[] = [
|
||||
factory: ({ configApi }) => AcmeAnalytics.fromConfig(configApi),
|
||||
}),
|
||||
];
|
||||
|
||||
// Or, when building for the new frontend system:
|
||||
import { AnalyticsBlueprint } from '@backstage/frontend-plugin-api';
|
||||
|
||||
export const acmeAnalyticsImplementation = AnalyticsBlueprint.make({
|
||||
name: 'acme',
|
||||
params: define =>
|
||||
define({
|
||||
deps: { configApi: configApiRef },
|
||||
factory: ({ configApi }) => AcmeAnalytics.fromConfig(configApi),
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
If you are integrating with an analytics service (as opposed to an internal
|
||||
|
||||
@@ -99,7 +99,7 @@ export { alertApiRef };
|
||||
|
||||
export { AlertMessage };
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export type AnalyticsApi = {
|
||||
captureEvent(event: AnalyticsEvent): void;
|
||||
};
|
||||
@@ -107,6 +107,30 @@ export type AnalyticsApi = {
|
||||
// @public
|
||||
export const analyticsApiRef: ApiRef<AnalyticsApi>;
|
||||
|
||||
// @public
|
||||
export const AnalyticsBlueprint: ExtensionBlueprint<{
|
||||
kind: 'analytics';
|
||||
name: undefined;
|
||||
params: <TDeps extends { [name in string]: unknown }>(
|
||||
params: AnalyticsImplementationFactory<TDeps>,
|
||||
) => ExtensionBlueprintParams<AnalyticsImplementationFactory<{}>>;
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnalyticsImplementationFactory<{}>,
|
||||
'core.analytics.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
config: {};
|
||||
configInput: {};
|
||||
dataRefs: {
|
||||
factory: ConfigurableExtensionDataRef<
|
||||
AnalyticsImplementationFactory<{}>,
|
||||
'core.analytics.factory',
|
||||
{}
|
||||
>;
|
||||
};
|
||||
}>;
|
||||
|
||||
// @public
|
||||
export const AnalyticsContext: (options: {
|
||||
attributes: Partial<AnalyticsContextValue>;
|
||||
@@ -135,6 +159,21 @@ export type AnalyticsEventAttributes = {
|
||||
[attribute in string]: string | boolean | number;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type AnalyticsImplementation = {
|
||||
captureEvent(event: AnalyticsEvent): void;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type AnalyticsImplementationFactory<
|
||||
Deps extends {
|
||||
[name in string]: unknown;
|
||||
} = {},
|
||||
> = {
|
||||
deps: TypesToApiRefs<Deps>;
|
||||
factory(deps: Deps): AnalyticsImplementation;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type AnalyticsTracker = {
|
||||
captureEvent: (
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { ApiRef, createApiRef } from '@backstage/core-plugin-api';
|
||||
import { AnalyticsContextValue } from '../../analytics/types';
|
||||
import type { AnalyticsBlueprint } from '../../blueprints/';
|
||||
|
||||
/**
|
||||
* Represents an event worth tracking in an analytics system that could inform
|
||||
@@ -102,6 +103,26 @@ export type AnalyticsTracker = {
|
||||
) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Analytics implementations are used to track user behavior in a Backstage
|
||||
* instance.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* To instrument your App or Plugin, retrieve an analytics tracker using the
|
||||
* `useAnalytics()` hook. This will return a pre-configured `AnalyticsTracker`
|
||||
* with relevant methods for instrumentation.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type AnalyticsImplementation = {
|
||||
/**
|
||||
* Primary event handler responsible for compiling and forwarding events to
|
||||
* an analytics system.
|
||||
*/
|
||||
captureEvent(event: AnalyticsEvent): void;
|
||||
};
|
||||
|
||||
/**
|
||||
* The Analytics API is used to track user behavior in a Backstage instance.
|
||||
*
|
||||
@@ -112,6 +133,8 @@ export type AnalyticsTracker = {
|
||||
* with relevant methods for instrumentation.
|
||||
*
|
||||
* @public
|
||||
* @deprecated This type is now deprecated and will be removed in an upcoming
|
||||
* release. Use the {@link AnalyticsImplementation} type instead.
|
||||
*/
|
||||
export type AnalyticsApi = {
|
||||
/**
|
||||
@@ -124,6 +147,11 @@ export type AnalyticsApi = {
|
||||
/**
|
||||
* The API reference of {@link AnalyticsApi}.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* To define a concrete Analytics Implementation, use {@link AnalyticsBlueprint}
|
||||
* instead.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const analyticsApiRef: ApiRef<AnalyticsApi> = createApiRef({
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AnalyticsBlueprint } from './AnalyticsBlueprint';
|
||||
|
||||
describe('AnalyticsBlueprint', () => {
|
||||
it('should create an extension with sensible defaults', () => {
|
||||
const factory = {
|
||||
deps: {},
|
||||
factory: () => ({ captureEvent: () => {} }),
|
||||
};
|
||||
|
||||
const extension = AnalyticsBlueprint.make({
|
||||
params: define => define(factory),
|
||||
name: 'test',
|
||||
});
|
||||
|
||||
expect(extension).toMatchInlineSnapshot(`
|
||||
{
|
||||
"$$type": "@backstage/ExtensionDefinition",
|
||||
"T": undefined,
|
||||
"attachTo": [
|
||||
{
|
||||
"id": "api:app/analytics",
|
||||
"input": "analyticsImplementations",
|
||||
},
|
||||
],
|
||||
"configSchema": undefined,
|
||||
"disabled": false,
|
||||
"factory": [Function],
|
||||
"inputs": {},
|
||||
"kind": "analytics",
|
||||
"name": "test",
|
||||
"output": [
|
||||
[Function],
|
||||
],
|
||||
"override": [Function],
|
||||
"toString": [Function],
|
||||
"version": "v2",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AnalyticsImplementation, TypesToApiRefs } from '../apis';
|
||||
import {
|
||||
createExtensionBlueprint,
|
||||
createExtensionBlueprintParams,
|
||||
createExtensionDataRef,
|
||||
} from '../wiring';
|
||||
|
||||
/** @public */
|
||||
export type AnalyticsImplementationFactory<
|
||||
Deps extends { [name in string]: unknown } = {},
|
||||
> = {
|
||||
deps: TypesToApiRefs<Deps>;
|
||||
factory(deps: Deps): AnalyticsImplementation;
|
||||
};
|
||||
|
||||
const factoryDataRef =
|
||||
createExtensionDataRef<AnalyticsImplementationFactory>().with({
|
||||
id: 'core.analytics.factory',
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates analytics implementations.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const AnalyticsBlueprint = createExtensionBlueprint({
|
||||
kind: 'analytics',
|
||||
attachTo: [{ id: 'api:app/analytics', input: 'analyticsImplementations' }],
|
||||
output: [factoryDataRef],
|
||||
dataRefs: {
|
||||
factory: factoryDataRef,
|
||||
},
|
||||
defineParams: <TDeps extends { [name in string]: unknown }>(
|
||||
params: AnalyticsImplementationFactory<TDeps>,
|
||||
) => createExtensionBlueprintParams(params as AnalyticsImplementationFactory),
|
||||
*factory(params) {
|
||||
yield factoryDataRef(params);
|
||||
},
|
||||
});
|
||||
@@ -14,6 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
AnalyticsBlueprint,
|
||||
type AnalyticsImplementationFactory,
|
||||
} from './AnalyticsBlueprint';
|
||||
export { ApiBlueprint } from './ApiBlueprint';
|
||||
export { AppRootElementBlueprint } from './AppRootElementBlueprint';
|
||||
export { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { AnalyticsImplementationFactory } from '@backstage/frontend-plugin-api';
|
||||
import { AnyApiFactory } from '@backstage/frontend-plugin-api';
|
||||
import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
|
||||
import { ApiFactory } from '@backstage/frontend-plugin-api';
|
||||
@@ -229,8 +230,6 @@ const appPlugin: FrontendPlugin<
|
||||
) => ExtensionBlueprintParams<AnyApiFactory>;
|
||||
}>;
|
||||
'api:app/analytics': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
name: 'analytics';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
@@ -238,7 +237,21 @@ const appPlugin: FrontendPlugin<
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
inputs: {
|
||||
analyticsImplementations: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
AnalyticsImplementationFactory<{}>,
|
||||
'core.analytics.factory',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: 'api';
|
||||
name: 'analytics';
|
||||
params: <
|
||||
TApi,
|
||||
TImpl extends TApi,
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import {
|
||||
AlertApiForwarder,
|
||||
NoOpAnalyticsApi,
|
||||
ErrorApiForwarder,
|
||||
ErrorAlerter,
|
||||
GoogleAuth,
|
||||
@@ -40,7 +39,6 @@ import {
|
||||
|
||||
import {
|
||||
alertApiRef,
|
||||
analyticsApiRef,
|
||||
errorApiRef,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
@@ -70,6 +68,7 @@ import {
|
||||
IdentityPermissionApi,
|
||||
} from '@backstage/plugin-permission-react';
|
||||
import { DefaultDialogApi } from './apis/DefaultDialogApi';
|
||||
import { AnalyticsApi } from './extensions/AnalyticsApi';
|
||||
|
||||
export const apis = [
|
||||
ApiBlueprint.make({
|
||||
@@ -102,15 +101,7 @@ export const apis = [
|
||||
factory: () => new AlertApiForwarder(),
|
||||
}),
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'analytics',
|
||||
params: defineParams =>
|
||||
defineParams({
|
||||
api: analyticsApiRef,
|
||||
deps: {},
|
||||
factory: () => new NoOpAnalyticsApi(),
|
||||
}),
|
||||
}),
|
||||
AnalyticsApi,
|
||||
ApiBlueprint.make({
|
||||
name: 'error',
|
||||
params: defineParams =>
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
import { AnalyticsApi } from './AnalyticsApi';
|
||||
import {
|
||||
AnalyticsBlueprint,
|
||||
AnalyticsImplementation,
|
||||
ApiBlueprint,
|
||||
configApiRef,
|
||||
createExtension,
|
||||
identityApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
describe('AnalyticsApi', () => {
|
||||
const mockEvent = {
|
||||
action: '',
|
||||
subject: '',
|
||||
context: {
|
||||
pluginId: '',
|
||||
extensionId: '',
|
||||
routeRef: '',
|
||||
},
|
||||
};
|
||||
const captureEventSpy1 = jest.fn();
|
||||
const MockImplementation1 = createExtension({
|
||||
name: 'mock-implementation-1',
|
||||
attachTo: { id: 'api:analytics', input: 'analyticsImplementations' },
|
||||
output: [AnalyticsBlueprint.dataRefs.factory],
|
||||
factory() {
|
||||
return [
|
||||
AnalyticsBlueprint.dataRefs.factory({
|
||||
deps: { configApi: configApiRef },
|
||||
factory: deps => ({
|
||||
captureEvent: event => captureEventSpy1(event, deps),
|
||||
}),
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
const captureEventSpy2 = jest.fn();
|
||||
const MockImplementation2 = createExtension({
|
||||
name: 'mock-implementation-2',
|
||||
attachTo: { id: 'api:analytics', input: 'analyticsImplementations' },
|
||||
output: [AnalyticsBlueprint.dataRefs.factory],
|
||||
factory() {
|
||||
return [
|
||||
AnalyticsBlueprint.dataRefs.factory({
|
||||
deps: { config: configApiRef, identityApi: identityApiRef },
|
||||
factory: deps => ({
|
||||
captureEvent: event => captureEventSpy2(event, deps),
|
||||
}),
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('wires up a single AnalyticsImplementationFactory', async () => {
|
||||
// Given the Analytics API and a single mock implementation
|
||||
const tester = createExtensionTester(AnalyticsApi).add(MockImplementation1);
|
||||
const apiFactory = tester.get(ApiBlueprint.dataRefs.factory);
|
||||
|
||||
// Then the API's deps should contain the mock implementation's deps.
|
||||
expect(apiFactory.deps).toMatchObject({
|
||||
'core.config': expect.anything(),
|
||||
});
|
||||
|
||||
// And the returned instance should be an Analytics Implementation
|
||||
const concreteImplementation = apiFactory.factory({
|
||||
'core.config': 'ConfigApiImplementation',
|
||||
});
|
||||
expect(concreteImplementation).toHaveProperty('captureEvent');
|
||||
|
||||
// When the resulting API's captureEvent method is called
|
||||
(concreteImplementation as AnalyticsImplementation).captureEvent(mockEvent);
|
||||
|
||||
// Then the mock implementation's captureEvent should have been called
|
||||
expect(captureEventSpy1.mock.calls[0][0]).toEqual(mockEvent);
|
||||
|
||||
// And the mock's factory should have resolved the expected deps
|
||||
expect(captureEventSpy1.mock.calls[0][1]).toEqual({
|
||||
configApi: 'ConfigApiImplementation',
|
||||
});
|
||||
});
|
||||
|
||||
it('wires up more than one AnalyticsImplementationFactory', () => {
|
||||
// Given the Analytics API and two mock implementations
|
||||
const tester = createExtensionTester(AnalyticsApi)
|
||||
.add(MockImplementation1)
|
||||
.add(MockImplementation2);
|
||||
const apiFactory = tester.get(ApiBlueprint.dataRefs.factory);
|
||||
|
||||
// Then the API's deps should contain the mock implementations' deps.
|
||||
expect(apiFactory.deps).toMatchObject({
|
||||
'core.config': expect.anything(),
|
||||
'core.identity': expect.anything(),
|
||||
});
|
||||
|
||||
// And the returned instance should be an Analytics Implementation
|
||||
const concreteImplementation = apiFactory.factory({
|
||||
'core.config': 'ConfigApiImplementation',
|
||||
'core.identity': 'IdentityApiImplementation',
|
||||
});
|
||||
expect(concreteImplementation).toHaveProperty('captureEvent');
|
||||
|
||||
// When the resulting API's captureEvent method is called
|
||||
(concreteImplementation as AnalyticsImplementation).captureEvent(mockEvent);
|
||||
|
||||
// Then the 1st mock implementation's captureEvent should have been called
|
||||
expect(captureEventSpy1.mock.calls[0][0]).toEqual(mockEvent);
|
||||
|
||||
// And the 1st mock's factory should have resolved the expected deps
|
||||
expect(captureEventSpy1.mock.calls[0][1]).toEqual({
|
||||
configApi: 'ConfigApiImplementation',
|
||||
});
|
||||
|
||||
// And the 2nd mock implementation's captureEvent should have been called
|
||||
expect(captureEventSpy2.mock.calls[0][0]).toEqual(mockEvent);
|
||||
|
||||
// And the 2nd mock's factory should have resolved the expected deps
|
||||
expect(captureEventSpy2.mock.calls[0][1]).toEqual({
|
||||
config: 'ConfigApiImplementation',
|
||||
identityApi: 'IdentityApiImplementation',
|
||||
});
|
||||
});
|
||||
|
||||
it('works fine with no AnalyticsImplementationFactory instances provided', () => {
|
||||
// Given the Analytics API and no mock implementations
|
||||
const tester = createExtensionTester(AnalyticsApi);
|
||||
const apiFactory = tester.get(ApiBlueprint.dataRefs.factory);
|
||||
|
||||
// Then the API's deps should be empty
|
||||
expect(apiFactory.deps).toEqual({});
|
||||
|
||||
// And the returned instance should be an Analytics Implementation
|
||||
const concreteImplementation = apiFactory.factory({});
|
||||
expect(concreteImplementation).toHaveProperty('captureEvent');
|
||||
|
||||
// Invoking the API's captureEvent method should result in no errors
|
||||
expect(() =>
|
||||
(concreteImplementation as AnalyticsImplementation).captureEvent(
|
||||
mockEvent,
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
analyticsApiRef,
|
||||
AnalyticsBlueprint,
|
||||
ApiBlueprint,
|
||||
ApiRef,
|
||||
createExtensionInput,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
export const AnalyticsApi = ApiBlueprint.makeWithOverrides({
|
||||
name: 'analytics',
|
||||
inputs: {
|
||||
analyticsImplementations: createExtensionInput([
|
||||
AnalyticsBlueprint.dataRefs.factory,
|
||||
]),
|
||||
},
|
||||
factory(originalFactory, { inputs }) {
|
||||
// Pull out and aggregate deps from every implementation input into an
|
||||
// object keyed by the apiRef ID to be passed to this API implementation as
|
||||
// if they were its own deps.
|
||||
const aggregatedDeps = inputs.analyticsImplementations
|
||||
.flatMap<ApiRef<unknown>>(impls =>
|
||||
Object.values(impls.get(AnalyticsBlueprint.dataRefs.factory).deps),
|
||||
)
|
||||
.reduce<{ [x: string]: ApiRef<unknown> }>((accum, ref) => {
|
||||
accum[ref.id] = ref;
|
||||
return accum;
|
||||
}, {});
|
||||
|
||||
return originalFactory(defineParams =>
|
||||
defineParams({
|
||||
api: analyticsApiRef,
|
||||
deps: aggregatedDeps,
|
||||
factory: analyticsApiDeps => {
|
||||
const actualApis = inputs.analyticsImplementations
|
||||
.map(impl => impl.get(AnalyticsBlueprint.dataRefs.factory))
|
||||
.map(({ factory, deps }) =>
|
||||
factory(
|
||||
// Reconstruct a deps argument to pass to this analytics
|
||||
// implementation factory from those passed into ours.
|
||||
Object.keys(deps).reduce<{ [x: string]: ApiRef<unknown> }>(
|
||||
(accum, dep) => {
|
||||
accum[dep] = analyticsApiDeps[
|
||||
(deps as { [x: string]: ApiRef<unknown> })[dep].id
|
||||
] as ApiRef<unknown>;
|
||||
return accum;
|
||||
},
|
||||
{},
|
||||
),
|
||||
),
|
||||
);
|
||||
return {
|
||||
captureEvent: event => {
|
||||
actualApis.forEach(api => {
|
||||
try {
|
||||
api.captureEvent(event);
|
||||
} catch {
|
||||
/* ignored */
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user