fix(frontend-plugin-api): default api config error

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2024-01-25 11:04:20 +01:00
parent 01abfa9c2b
commit b1afb710b8
10 changed files with 51 additions and 24 deletions
+1
View File
@@ -47,6 +47,7 @@
},
"dependencies": {
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/types": "workspace:^",
"@backstage/version-bridge": "workspace:^",
"@types/react": "^16.13.1 || ^17.0.0",
@@ -17,6 +17,7 @@
import React, { PropsWithChildren } from 'react';
import { ApiRef, ApiHolder, TypesToApiRefs } from './types';
import { useVersionedContext } from '@backstage/version-bridge';
import { NotImplementedError } from '@backstage/errors';
/**
* React hook for retrieving {@link ApiHolder}, an API catalog.
@@ -26,12 +27,12 @@ import { useVersionedContext } from '@backstage/version-bridge';
export function useApiHolder(): ApiHolder {
const versionedHolder = useVersionedContext<{ 1: ApiHolder }>('api-context');
if (!versionedHolder) {
throw new Error('API context is not available');
throw new NotImplementedError('API context is not available');
}
const apiHolder = versionedHolder.atVersion(1);
if (!apiHolder) {
throw new Error('ApiContext v1 not available');
throw new NotImplementedError('ApiContext v1 not available');
}
return apiHolder;
}
@@ -47,7 +48,7 @@ export function useApi<T>(apiRef: ApiRef<T>): T {
const api = apiHolder.get(apiRef);
if (!api) {
throw new Error(`No implementation available for ${apiRef}`);
throw new NotImplementedError(`No implementation available for ${apiRef}`);
}
return api;
}
@@ -73,7 +74,9 @@ export function withApis<T extends {}>(apis: TypesToApiRefs<T>) {
const api = apiHolder.get(ref);
if (!api) {
throw new Error(`No implementation available for ${ref}`);
throw new NotImplementedError(
`No implementation available for ${ref}`,
);
}
impls[key] = api;
}
@@ -211,8 +211,8 @@ describe('RouteTracker', () => {
action: 'navigate',
attributes: {},
context: {
extensionId: 'App',
pluginId: 'root',
extensionId: 'app',
pluginId: 'app',
},
subject: '/not-routable-extension',
value: undefined,
@@ -221,8 +221,8 @@ describe('RouteTracker', () => {
action: 'click',
attributes: undefined,
context: {
extensionId: 'App',
pluginId: 'root',
extensionId: 'app',
pluginId: 'app',
},
subject: 'test',
value: undefined,
@@ -35,8 +35,8 @@ describe('AnalyticsContext', () => {
it('returns default values', () => {
const { result } = renderHook(() => useAnalyticsContext());
expect(result.current).toEqual({
extensionId: 'App',
pluginId: 'root',
extensionId: 'app',
pluginId: 'app',
});
});
});
@@ -49,8 +49,8 @@ describe('AnalyticsContext', () => {
</AnalyticsContext>,
);
expect(result.getByTestId('extension-id')).toHaveTextContent('App');
expect(result.getByTestId('plugin-id')).toHaveTextContent('root');
expect(result.getByTestId('extension-id')).toHaveTextContent('app');
expect(result.getByTestId('plugin-id')).toHaveTextContent('app');
});
it('uses provided analytics context', () => {
@@ -60,7 +60,7 @@ describe('AnalyticsContext', () => {
</AnalyticsContext>,
);
expect(result.getByTestId('extension-id')).toHaveTextContent('App');
expect(result.getByTestId('extension-id')).toHaveTextContent('app');
expect(result.getByTestId('plugin-id')).toHaveTextContent('custom');
});
@@ -38,7 +38,7 @@ export const useAnalyticsContext = (): AnalyticsContextValue => {
if (theContext === undefined) {
return {
pluginId: 'app',
extensionId: 'App',
extensionId: 'app',
};
}
@@ -53,8 +53,8 @@ describe('useAnalytics', () => {
some: 'value',
},
context: {
extensionId: 'App',
pluginId: 'root',
extensionId: 'app',
pluginId: 'app',
},
});
});
@@ -23,8 +23,11 @@ import { Tracker } from './Tracker';
function useAnalyticsApi(): AnalyticsApi {
try {
return useApi(analyticsApiRef);
} catch {
return { captureEvent: () => {} };
} catch (error) {
if (error.name === 'NotImplementedError') {
return { captureEvent: () => {} };
}
throw error;
}
}
@@ -493,8 +493,8 @@ describe('GoogleAnalytics4', () => {
});
});
describe('Backward compatibility', () => {
it('fallback category for legacy context extension property', () => {
describe('api backward compatibility', () => {
it('continue working with legacy App category', () => {
const api = GoogleAnalytics4.fromConfig(basicValidConfig);
expect(api.captureEvent).toBeDefined();
@@ -512,6 +512,24 @@ describe('GoogleAnalytics4', () => {
});
});
it('use lowercase app as the new default category', () => {
const api = GoogleAnalytics4.fromConfig(basicValidConfig);
expect(api.captureEvent).toBeDefined();
api.captureEvent({
action: 'navigate',
subject: '/',
context: { ...context, extensionId: '', extension: '' },
});
expect(fnEvent).toHaveBeenCalledWith('page_view', {
action: 'page_view',
label: '/',
category: 'app',
});
});
it('prioritize new context extension id over old extension property', () => {
const api = GoogleAnalytics4.fromConfig(basicValidConfig);
@@ -520,13 +538,13 @@ describe('GoogleAnalytics4', () => {
api.captureEvent({
action: 'navigate',
subject: '/',
context: { ...context, extensionId: 'App', extension: '' },
context: { ...context, extensionId: 'app', extension: '' },
});
expect(fnEvent).toHaveBeenCalledWith('page_view', {
action: 'page_view',
label: '/',
category: 'App',
category: 'app',
});
});
});
@@ -171,9 +171,10 @@ export class GoogleAnalytics4 implements AnalyticsApi, NewAnalyticsApi {
}
const extensionId = context.extensionId || context.extension;
const category = extensionId ? String(extensionId) : 'App';
const category = extensionId ? String(extensionId) : 'app';
if (action === 'navigate' && category === 'App') {
// The legacy default extension was 'App' and the new one is 'app'
if (action === 'navigate' && category.toLocaleLowerCase() === 'app') {
this.capture.event(
{
category,
+1
View File
@@ -3896,6 +3896,7 @@ __metadata:
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/core-app-api": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@backstage/types": "workspace:^"
"@backstage/version-bridge": "workspace:^"