From 311dda9e18df096cbffdcecaad08f628d3d154cf Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 30 Jan 2024 11:31:46 +0100 Subject: [PATCH] feat: add backward compatibility for core anlytics api implementations Signed-off-by: Camila Belo --- .changeset/blue-keys-do.md | 2 +- .changeset/gorgeous-pumas-draw.md | 2 +- packages/frontend-plugin-api/api-report.md | 16 ++++ .../AnalyticsApi/MultipleAnalyticsApi.test.ts | 64 ++++++++++++++++ .../AnalyticsApi/MultipleAnalyticsApi.ts | 76 +++++++++++++++++++ .../AnalyticsApi/NoOpAnalyticsApi.ts | 30 ++++++++ .../implementations/AnalyticsApi/index.ts | 18 +++++ .../src/apis/implementations/index.ts | 20 +++++ .../frontend-plugin-api/src/apis/index.ts | 1 + 9 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts create mode 100644 packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts create mode 100644 packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts create mode 100644 packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/index.ts create mode 100644 packages/frontend-plugin-api/src/apis/implementations/index.ts diff --git a/.changeset/blue-keys-do.md b/.changeset/blue-keys-do.md index c933bac0f2..81e79a7975 100644 --- a/.changeset/blue-keys-do.md +++ b/.changeset/blue-keys-do.md @@ -2,4 +2,4 @@ '@backstage/frontend-plugin-api': minor --- -**BREAKING**: Replace default plugin extension and plugin ids to be `app` instead of `root`. +**BREAKING**: Replace default plugin extension and plugin ids to be `app` instead of `root` and update the `MultipleAnalyticsApi` to support new Analytics API context type . diff --git a/.changeset/gorgeous-pumas-draw.md b/.changeset/gorgeous-pumas-draw.md index d2ae0370e5..c5b2214829 100644 --- a/.changeset/gorgeous-pumas-draw.md +++ b/.changeset/gorgeous-pumas-draw.md @@ -4,4 +4,4 @@ '@backstage/plugin-analytics-module-ga': minor --- -**BREAKING**: Add support to the new analytics api and use `app` as default extension and plugin ids. It is breaking only because you will start seeing app as the new default id for extensions and plugins events. +Add support to the new analytics api. diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 4116469c2a..21d329fae8 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -8,6 +8,8 @@ import { AlertApi } from '@backstage/core-plugin-api'; import { alertApiRef } from '@backstage/core-plugin-api'; import { AlertMessage } from '@backstage/core-plugin-api'; +import { AnalyticsApi as AnalyticsApi_2 } from '@backstage/core-plugin-api'; +import { AnalyticsEvent as AnalyticsEvent_2 } from '@backstage/core-plugin-api'; import { AnyApiFactory } from '@backstage/core-plugin-api'; import { AnyApiRef } from '@backstage/core-plugin-api'; import { ApiFactory } from '@backstage/core-plugin-api'; @@ -993,6 +995,20 @@ export { identityApiRef }; export { microsoftAuthApiRef }; +// @public +export class MultipleAnalyticsApi implements AnalyticsApi_2, AnalyticsApi { + captureEvent(event: AnalyticsEvent_2 | AnalyticsEvent): void; + static fromApis( + actualApis: (AnalyticsApi_2 | AnalyticsApi)[], + ): MultipleAnalyticsApi; +} + +// @public +export class NoOpAnalyticsApi implements AnalyticsApi_2, AnalyticsApi { + // (undocumented) + captureEvent(_event: AnalyticsEvent_2 | AnalyticsEvent): void; +} + export { OAuthApi }; export { OAuthRequestApi }; diff --git a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts new file mode 100644 index 0000000000..8095ee0020 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright 2024 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 { MultipleAnalyticsApi } from './MultipleAnalyticsApi'; + +describe('MultipleAnalyticsApi', () => { + const analyticsApiOne = { captureEvent: jest.fn() }; + const analyticsApiTwo = { captureEvent: jest.fn() }; + const multipleApis = MultipleAnalyticsApi.fromApis([ + analyticsApiOne, + analyticsApiTwo, + ]); + + const event = { + action: 'navivate', + subject: '/path', + context: { + extension: 'App', + pluginId: 'plugin', + routeRef: 'unknown', + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('forwards events to all apis', () => { + // When an event is captured + multipleApis.captureEvent(event); + + // Then both underlying APIs should have received the event + expect(analyticsApiOne.captureEvent).toHaveBeenCalledTimes(1); + expect(analyticsApiOne.captureEvent).toHaveBeenCalledWith(event); + expect(analyticsApiTwo.captureEvent).toHaveBeenCalledTimes(1); + expect(analyticsApiTwo.captureEvent).toHaveBeenCalledWith(event); + }); + + it('forwards events to all apis even if one throws an error', () => { + // Given one underlying API that throws on capture + analyticsApiOne.captureEvent.mockImplementation(() => { + throw new Error('!!!'); + }); + + // When an event is captured + multipleApis.captureEvent(event); + + // Then the other underlying API should have still received the event + expect(analyticsApiTwo.captureEvent).toHaveBeenCalledTimes(1); + expect(analyticsApiTwo.captureEvent).toHaveBeenCalledWith(event); + }); +}); diff --git a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts new file mode 100644 index 0000000000..b99607a5fb --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/MultipleAnalyticsApi.ts @@ -0,0 +1,76 @@ +/* + * Copyright 2024 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 { AnalyticsApi, AnalyticsEvent } from '@backstage/core-plugin-api'; +import { + AnalyticsApi as NewAnalyicsApi, + AnalyticsEvent as NewAnalyicsEvent, +} from '../../definitions'; + +/** + * An implementation of the AnalyticsApi that can be used to forward analytics + * events to multiple concrete implementations. + * + * @public + * + * @example + * + * ```jsx + * createApiFactory({ + * api: analyticsApiRef, + * deps: { configApi: configApiRef, identityApi: identityApiRef, storageApi: storageApiRef }, + * factory: ({ configApi, identityApi, storageApi }) => + * MultipleAnalyticsApi.fromApis([ + * VendorAnalyticsApi.fromConfig(configApi, { identityApi }), + * CustomAnalyticsApi.fromConfig(configApi, { identityApi, storageApi }), + * ]), + * }); + * ``` + */ +export class MultipleAnalyticsApi implements AnalyticsApi, NewAnalyicsApi { + private constructor( + private readonly actualApis: (AnalyticsApi | NewAnalyicsApi)[], + ) {} + + /** + * Create an AnalyticsApi implementation from an array of concrete + * implementations. + * + * @example + * + * ```jsx + * MultipleAnalyticsApi.fromApis([ + * SomeAnalyticsApi.fromConfig(configApi), + * new CustomAnalyticsApi(), + * ]); + * ``` + */ + static fromApis(actualApis: (AnalyticsApi | NewAnalyicsApi)[]) { + return new MultipleAnalyticsApi(actualApis); + } + + /** + * Forward the event to all configured analytics API implementations. + */ + captureEvent(event: AnalyticsEvent | NewAnalyicsEvent): void { + this.actualApis.forEach(analyticsApi => { + try { + analyticsApi.captureEvent(event as AnalyticsEvent & NewAnalyicsEvent); + } catch { + /* ignored */ + } + }); + } +} diff --git a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts new file mode 100644 index 0000000000..b64c31afaf --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/NoOpAnalyticsApi.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2024 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 { AnalyticsApi, AnalyticsEvent } from '@backstage/core-plugin-api'; +import { + AnalyticsApi as NewAnalyicsApi, + AnalyticsEvent as NewAnalyicsEvent, +} from '../../definitions'; + +/** + * Base implementation for the AnalyticsApi that does nothing. + * + * @public + */ +export class NoOpAnalyticsApi implements AnalyticsApi, NewAnalyicsApi { + captureEvent(_event: AnalyticsEvent | NewAnalyicsEvent): void {} +} diff --git a/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/index.ts b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/index.ts new file mode 100644 index 0000000000..c9e2008516 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/implementations/AnalyticsApi/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 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. + */ + +export { MultipleAnalyticsApi } from './MultipleAnalyticsApi'; +export { NoOpAnalyticsApi } from './NoOpAnalyticsApi'; diff --git a/packages/frontend-plugin-api/src/apis/implementations/index.ts b/packages/frontend-plugin-api/src/apis/implementations/index.ts new file mode 100644 index 0000000000..b3ec1c8513 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/implementations/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2020 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. + */ + +// This folder contains implementations for all core APIs. +// Plugins should rely on these APIs for functionality as much as possible. + +export * from './AnalyticsApi'; diff --git a/packages/frontend-plugin-api/src/apis/index.ts b/packages/frontend-plugin-api/src/apis/index.ts index 5def6cb0f9..983497271f 100644 --- a/packages/frontend-plugin-api/src/apis/index.ts +++ b/packages/frontend-plugin-api/src/apis/index.ts @@ -15,4 +15,5 @@ */ export * from './definitions'; +export * from './implementations'; export * from './system';