diff --git a/.changeset/analytics-haunts-dismembered-constellations.md b/.changeset/analytics-haunts-dismembered-constellations.md
index cc685d68f0..6acf7d8bca 100644
--- a/.changeset/analytics-haunts-dismembered-constellations.md
+++ b/.changeset/analytics-haunts-dismembered-constellations.md
@@ -9,15 +9,12 @@ metadata, allowing clicks to be attributed to the plugin containing the link:
```json
{
- "verb": "click",
- "noun": "/value/of-the/to-prop/passed-to-the-link",
- "domain": {
- "componentName": "Link",
+ "action": "click",
+ "subject": "/value/of-the/to-prop/passed-to-the-link",
+ "context": {
+ "componentName": "SomeAssociatedExtension",
"pluginId": "plugin-in-which-link-was-clicked",
"routeRef": "any-associated-route-ref-id"
}
}
```
-
-These events can be identified and handled by checking for the verb `click`
-and the componentName `Link`.
diff --git a/.changeset/analytics-old-skipping-record.md b/.changeset/analytics-old-skipping-record.md
index 9bfa58fd57..a0cacdf693 100644
--- a/.changeset/analytics-old-skipping-record.md
+++ b/.changeset/analytics-old-skipping-record.md
@@ -9,14 +9,14 @@ Backstage is being used. The API consists of the following:
- `useAnalytics()`, a hook to be used inside plugin components which retrieves
an Analytics Tracker.
- `tracker.captureEvent()`, a method on the tracker used to instrument key
- events. The method expects a verb (the action performed), a noun (a unique
- identifier of the object the action is being taken on), and optionally a
- numeric metric/value.
-- ``, a way to declaratively attach additional information
+ events. The method expects an action (the event name) and a subject (a unique
+ identifier of the object the action is being taken on).
+- ``, a way to declaratively attach additional information
to any/all events captured in the underlying React tree. There is also a
- `withAnalyticsDomain()` HOC utility.
-- The `tracker.captureEvent()` method also accepts a `context` object for
- optionally providing additional run-time information about an event.
+ `withAnalyticsContext()` HOC utility.
+- The `tracker.captureEvent()` method also accepts an `attributes` option for
+ providing additional run-time information about an event, as well as a
+ `value` option for capturing a numeric/metric value.
By default, captured events are not sent anywhere. In order to collect and
redirect events to an analytics system, the `analyticsApi` will need to be
diff --git a/.changeset/analytics-sings-stormy-weather.md b/.changeset/analytics-sings-stormy-weather.md
index 2a0d83e8a4..a04e824b60 100644
--- a/.changeset/analytics-sings-stormy-weather.md
+++ b/.changeset/analytics-sings-stormy-weather.md
@@ -6,13 +6,13 @@ The Core App API now automatically instruments all route location changes using
the new Analytics API. Each location change triggers a `navigate` event, which
is an analogue of a "pageview" event in traditional web analytics systems. In
addition to the path, these events provide plugin-level metadata via the
-analytics domain, which can be useful for analyzing plugin usage:
+analytics context, which can be useful for analyzing plugin usage:
```json
{
- "verb": "navigate",
- "noun": "/the-path/navigated/to?with=params#and-hashes",
- "domain": {
+ "action": "navigate",
+ "subject": "/the-path/navigated/to?with=params#and-hashes",
+ "context": {
"componentName": "App",
"pluginId": "id-of-plugin-that-exported-the-route",
"routeRef": "associated-route-ref-id"
@@ -20,5 +20,5 @@ analytics domain, which can be useful for analyzing plugin usage:
}
```
-These events can be identified and handled by checking for the verb `navigate`
-and the componentName `App`.
+These events can be identified and handled by checking for the action
+`navigate` and the componentName `App`.
diff --git a/packages/core-app-api/src/app/App.test.tsx b/packages/core-app-api/src/app/App.test.tsx
index 93b711036b..b04cf3edbf 100644
--- a/packages/core-app-api/src/app/App.test.tsx
+++ b/packages/core-app-api/src/app/App.test.tsx
@@ -377,22 +377,21 @@ describe('Integration Test', () => {
,
);
- // Capture initial and subsequent navigation events with expected domain
- // values.
+ // Capture initial and subsequent navigation events with expected context.
const capturedEvents = mockAnalyticsApi.getEvents();
expect(capturedEvents[0]).toMatchObject({
- verb: 'navigate',
- noun: '/',
- domain: {
+ action: 'navigate',
+ subject: '/',
+ context: {
componentName: 'App',
pluginId: 'blob',
routeRef: 'ref-1-2',
},
});
expect(capturedEvents[1]).toMatchObject({
- verb: 'navigate',
- noun: '/foo',
- domain: {
+ action: 'navigate',
+ subject: '/foo',
+ context: {
componentName: 'App',
pluginId: 'plugin2',
routeRef: 'ref-2',
diff --git a/packages/core-app-api/src/routing/RouteTracker.tsx b/packages/core-app-api/src/routing/RouteTracker.tsx
index 1114bdcc70..8ca76be78e 100644
--- a/packages/core-app-api/src/routing/RouteTracker.tsx
+++ b/packages/core-app-api/src/routing/RouteTracker.tsx
@@ -24,23 +24,23 @@ import {
BackstagePlugin,
useAnalytics,
getComponentData,
- AnalyticsDomain,
- RoutableAnalyticsDomain,
+ AnalyticsContext,
+ CommonAnalyticsContext,
} from '@backstage/core-plugin-api';
type RouteObjects = ReturnType;
/**
- * Returns an extension domain given the current pathname and a RouteObject
+ * Returns an extension context given the current pathname and a RouteObject
* that defines all registered routes in react.
*
* If no exact match is found, path parts are stripped away, one-by-one, until
* a parent-level path matches a route.
*/
-const getExtensionDomain = (
+const getExtensionContext = (
pathname: string,
routes: RouteObjects,
-): RoutableAnalyticsDomain | {} => {
+): CommonAnalyticsContext | {} => {
try {
const cleanPath = pathname.replace(/\/+$/, '');
const matches = matchRoutes(routes, { pathname });
@@ -76,7 +76,7 @@ const getExtensionDomain = (
// Try again, one path-level shallower.
const nextLevelPath = cleanPath.split('/').slice(0, -1).join('/');
return nextLevelPath !== ''
- ? getExtensionDomain(nextLevelPath, routes)
+ ? getExtensionContext(nextLevelPath, routes)
: {};
} catch {
return {};
@@ -106,16 +106,16 @@ const TrackNavigation = ({
};
/**
- * Logs a "navigate" event with appropriate plugin-level analytics domain
+ * Logs a "navigate" event with appropriate plugin-level analytics context
* attributes each time the user navigates to a page.
*/
export const RouteTracker = ({ objects }: { objects: RouteObjects }) => {
const { pathname, search, hash } = useLocation();
- const attributes = getExtensionDomain(pathname, objects);
+ const attributes = getExtensionContext(pathname, objects);
return (
-
+
-
+
);
};
diff --git a/packages/core-components/src/components/Link/Link.test.tsx b/packages/core-components/src/components/Link/Link.test.tsx
index 504de6bb9a..e2e8612fc9 100644
--- a/packages/core-components/src/components/Link/Link.test.tsx
+++ b/packages/core-components/src/components/Link/Link.test.tsx
@@ -63,9 +63,9 @@ describe('', () => {
// Analytics event should have been fired.
expect(analyticsApi.getEvents()[0]).toMatchObject({
- verb: 'click',
- noun: '/test',
- domain: { componentName: 'Link' },
+ action: 'click',
+ subject: '/test',
+ context: { componentName: 'Link' },
});
// Custom onClick handler should have still been fired too.
diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx
index 7b26ea5037..1134bc1b07 100644
--- a/packages/core-components/src/components/Link/Link.tsx
+++ b/packages/core-components/src/components/Link/Link.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useAnalytics, withAnalyticsDomain } from '@backstage/core-plugin-api';
+import { useAnalytics, withAnalyticsContext } from '@backstage/core-plugin-api';
import {
Link as MaterialLink,
LinkProps as MaterialLinkProps,
@@ -41,7 +41,7 @@ declare function LinkType(props: LinkProps): JSX.Element;
* - Makes the Link use react-router
* - Captures Link clicks as analytics events.
*/
-const ActualLink = withAnalyticsDomain(
+const ActualLink = withAnalyticsContext(
({ inputRef, onClick, ...props }: LinkProps & { inputRef: OptionalRef }) => {
const analytics = useAnalytics();
const to = String(props.to);
diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md
index 2628c13ad1..d531244cd7 100644
--- a/packages/core-plugin-api/api-report.md
+++ b/packages/core-plugin-api/api-report.md
@@ -38,7 +38,7 @@ export type AlertMessage = {
//
// @public
export type AnalyticsApi = {
- captureEvent(event: DomainDecoratedAnalyticsEvent): void;
+ captureEvent(event: AnalyticsEvent): void;
};
// Warning: (ae-missing-release-tag) "analyticsApiRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -46,38 +46,40 @@ export type AnalyticsApi = {
// @public (undocumented)
export const analyticsApiRef: ApiRef;
-// Warning: (ae-missing-release-tag) "AnalyticsDomain" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+// Warning: (ae-missing-release-tag) "AnalyticsContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
-export const AnalyticsDomain: ({
+export const AnalyticsContext: ({
attributes,
children,
}: {
- attributes: AnalyticsDomainValue;
+ attributes: AnalyticsContextValue;
children: ReactNode;
}) => JSX.Element;
-// Warning: (ae-missing-release-tag) "AnalyticsDomainValue" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+// Warning: (ae-forgotten-export) The symbol "AnyAnalyticsContext" needs to be exported by the entry point index.d.ts
+// Warning: (ae-missing-release-tag) "AnalyticsContextValue" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
-export type AnalyticsDomainValue = Partial<
- RoutableAnalyticsDomain & ComponentAnalyticsDomain & AnyAnalyticsDomain
+export type AnalyticsContextValue = Partial<
+ CommonAnalyticsContext & AnyAnalyticsContext
>;
// Warning: (ae-missing-release-tag) "AnalyticsEvent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export type AnalyticsEvent = {
- verb: string;
- noun: string;
+ action: string;
+ subject: string;
value?: number;
- context?: AnalyticsEventContext;
+ attributes?: AnalyticsEventAttributes;
+ context: AnalyticsContextValue;
};
-// Warning: (ae-missing-release-tag) "AnalyticsEventContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+// Warning: (ae-missing-release-tag) "AnalyticsEventAttributes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
-export type AnalyticsEventContext = {
+export type AnalyticsEventAttributes = {
[attribute in string]: string | boolean | number;
};
@@ -86,20 +88,15 @@ export type AnalyticsEventContext = {
// @public
export type AnalyticsTracker = {
captureEvent: (
- verb: string,
- noun: string,
- value?: number,
- context?: AnalyticsEventContext,
+ action: string,
+ subject: string,
+ options?: {
+ value?: number;
+ attributes?: AnalyticsEventAttributes;
+ },
) => void;
};
-// Warning: (ae-missing-release-tag) "AnyAnalyticsDomain" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public
-export type AnyAnalyticsDomain = {
- [param in string]: string | boolean | number | undefined;
-};
-
// Warning: (ae-missing-release-tag) "AnyApiFactory" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -300,11 +297,12 @@ export type BootErrorPageProps = {
error: Error;
};
-// Warning: (ae-missing-release-tag) "ComponentAnalyticsDomain" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+// Warning: (ae-missing-release-tag) "CommonAnalyticsContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
-export type ComponentAnalyticsDomain = {
+export type CommonAnalyticsContext = {
pluginId: string;
+ routeRef: string;
componentName: string;
};
@@ -440,13 +438,6 @@ export type DiscoveryApi = {
// @public (undocumented)
export const discoveryApiRef: ApiRef;
-// Warning: (ae-missing-release-tag) "DomainDecoratedAnalyticsEvent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public
-export type DomainDecoratedAnalyticsEvent = AnalyticsEvent & {
- domain: AnalyticsDomainValue;
-};
-
// Warning: (ae-missing-release-tag) "ElementCollection" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
@@ -790,15 +781,6 @@ export type ProfileInfoApi = {
getProfile(options?: AuthRequestOptions): Promise;
};
-// Warning: (ae-missing-release-tag) "RoutableAnalyticsDomain" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public
-export type RoutableAnalyticsDomain = {
- pluginId: string;
- routeRef: string;
- componentName: string;
-};
-
// Warning: (ae-missing-release-tag) "RouteOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -982,12 +964,12 @@ export function useRouteRefParams(
_routeRef: RouteRef | SubRouteRef,
): Params;
-// Warning: (ae-missing-release-tag) "withAnalyticsDomain" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+// Warning: (ae-missing-release-tag) "withAnalyticsContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
-export function withAnalyticsDomain
>
);
};
-describe('AnalyticsDomain', () => {
- describe('useAnalyticsDomain', () => {
+describe('AnalyticsContext', () => {
+ describe('useAnalyticsContext', () => {
it('returns default values', () => {
- const { result } = renderHook(() => useAnalyticsDomain());
+ const { result } = renderHook(() => useAnalyticsContext());
expect(result.current).toEqual({
componentName: 'App',
pluginId: 'root',
@@ -47,12 +47,12 @@ describe('AnalyticsDomain', () => {
});
});
- describe('AnalyticsDomain', () => {
- it('uses default analytics domain', () => {
+ describe('AnalyticsContext', () => {
+ it('uses default analytics context', () => {
const result = render(
-
-
- ,
+
+
+ ,
);
expect(result.getByTestId('component-name')).toHaveTextContent('App');
@@ -60,11 +60,11 @@ describe('AnalyticsDomain', () => {
expect(result.getByTestId('route-ref')).toHaveTextContent('unknown');
});
- it('uses provided analytics domain', () => {
+ it('uses provided analytics context', () => {
const result = render(
-
-
- ,
+
+
+ ,
);
expect(result.getByTestId('component-name')).toHaveTextContent('App');
@@ -72,29 +72,33 @@ describe('AnalyticsDomain', () => {
expect(result.getByTestId('route-ref')).toHaveTextContent('unknown');
});
- it('uses nested analytics domain', () => {
+ it('uses nested analytics context', () => {
const result = render(
-
-
-
-
- ,
+
+
+
+
+ ,
);
expect(result.getByTestId('component-name')).toHaveTextContent(
- 'DomainSpy',
+ 'AnalyticsSpy',
);
expect(result.getByTestId('plugin-id')).toHaveTextContent('custom');
expect(result.getByTestId('route-ref')).toHaveTextContent('unknown');
});
});
- describe('withAnalyticsDomain', () => {
- it('wraps component with analytics domain', () => {
- const DomainSpyHOC = withAnalyticsDomain(DomainSpy, { custom: 'attr' });
- const result = render();
+ describe('withAnalyticsContext', () => {
+ it('wraps component with analytics context', () => {
+ const AnalyticsSpyHOC = withAnalyticsContext(AnalyticsSpy, {
+ custom: 'attr',
+ });
+ const result = render();
expect(result.getByTestId('custom')).toHaveTextContent('attr');
- expect(DomainSpyHOC.displayName).toBe('WithAnalyticsDomain(DomainSpy)');
+ expect(AnalyticsSpyHOC.displayName).toBe(
+ 'WithAnalyticsContext(AnalyticsSpy)',
+ );
});
});
});
diff --git a/packages/core-plugin-api/src/analytics/AnalyticsDomain.tsx b/packages/core-plugin-api/src/analytics/AnalyticsContext.tsx
similarity index 50%
rename from packages/core-plugin-api/src/analytics/AnalyticsDomain.tsx
rename to packages/core-plugin-api/src/analytics/AnalyticsContext.tsx
index be1b90290c..f50375d42f 100644
--- a/packages/core-plugin-api/src/analytics/AnalyticsDomain.tsx
+++ b/packages/core-plugin-api/src/analytics/AnalyticsContext.tsx
@@ -15,9 +15,9 @@
*/
import React, { createContext, ReactNode, useContext } from 'react';
-import { AnalyticsDomainValue, AnyAnalyticsDomain } from './types';
+import { AnalyticsContextValue } from './types';
-export const AnalyticsDomainContext = createContext({
+const AnalyticsReactContext = createContext({
routeRef: 'unknown',
pluginId: 'root',
componentName: 'App',
@@ -25,61 +25,60 @@ export const AnalyticsDomainContext = createContext({
/**
* A "private" (to this package) hook that enables context inheritance and a
- * way to read Analytics Domain values at event capture-time.
+ * way to read Analytics Context values at event capture-time.
* @private
*/
-export const useAnalyticsDomain = () => {
- return useContext(AnalyticsDomainContext);
+export const useAnalyticsContext = () => {
+ return useContext(AnalyticsReactContext);
};
/**
- * Provides components in the child react tree an Analytics Domain, ensuring
- * all analytics events captured within the domain have relevant contextual
- * attributes.
+ * Provides components in the child react tree an Analytics Context, ensuring
+ * all analytics events captured within the context have relevant attributes.
*
- * Analytics domains are additive, meaning the domain ultimately emitted with
- * an event is the combination of all domains in the parent tree.
+ * Analytics contexts are additive, meaning the context ultimately emitted with
+ * an event is the combination of all contexts in the parent tree.
*/
-export const AnalyticsDomain = ({
+export const AnalyticsContext = ({
attributes,
children,
}: {
- attributes: AnalyticsDomainValue;
+ attributes: AnalyticsContextValue;
children: ReactNode;
}) => {
- const parentValues = useAnalyticsDomain();
+ const parentValues = useAnalyticsContext();
const combinedValue = {
...parentValues,
...attributes,
};
return (
-
+
{children}
-
+
);
};
/**
- * Returns an HOC wrapping the provided component in an Analytics Domain with
+ * Returns an HOC wrapping the provided component in an Analytics context with
* the given values.
*
- * @param Component - Component to be wrapped with analytics domain attributes.
- * @param domain - Analytics domain key/value pairs.
+ * @param Component - Component to be wrapped with analytics context attributes
+ * @param values - Analytics context key/value pairs.
*/
-export function withAnalyticsDomain
(
+export function withAnalyticsContext
(
Component: React.ComponentType
,
- domain: AnyAnalyticsDomain,
+ values: AnalyticsContextValue,
) {
- const ComponentWithAnalyticsDomain = (props: P) => {
+ const ComponentWithAnalyticsContext = (props: P) => {
return (
-
+
-
+
);
};
- ComponentWithAnalyticsDomain.displayName = `WithAnalyticsDomain(${
+ ComponentWithAnalyticsContext.displayName = `WithAnalyticsContext(${
Component.displayName || Component.name || 'Component'
})`;
- return ComponentWithAnalyticsDomain;
+ return ComponentWithAnalyticsContext;
}
diff --git a/packages/core-plugin-api/src/analytics/index.ts b/packages/core-plugin-api/src/analytics/index.ts
index 308069f9a8..dbf82aa4e3 100644
--- a/packages/core-plugin-api/src/analytics/index.ts
+++ b/packages/core-plugin-api/src/analytics/index.ts
@@ -14,11 +14,6 @@
* limitations under the License.
*/
-export { AnalyticsDomain, withAnalyticsDomain } from './AnalyticsDomain';
-export type {
- AnalyticsDomainValue,
- AnyAnalyticsDomain,
- ComponentAnalyticsDomain,
- RoutableAnalyticsDomain,
-} from './types';
+export { AnalyticsContext, withAnalyticsContext } from './AnalyticsContext';
+export type { AnalyticsContextValue, CommonAnalyticsContext } from './types';
export { useAnalytics } from './useAnalytics';
diff --git a/packages/core-plugin-api/src/analytics/types.ts b/packages/core-plugin-api/src/analytics/types.ts
index acbda41da9..189d182328 100644
--- a/packages/core-plugin-api/src/analytics/types.ts
+++ b/packages/core-plugin-api/src/analytics/types.ts
@@ -15,50 +15,35 @@
*/
/**
- * Analytics domain covering routable extensions.
+ * Common analytics context attributes.
*/
-export type RoutableAnalyticsDomain = {
+export type CommonAnalyticsContext = {
/**
- * The plugin that exposed the route.
+ * The associated plugin.
*/
pluginId: string;
/**
- * The ID of the route ref associated with the route.
+ * The ID of the associated route ref.
*/
routeRef: string;
/**
- * The name of the component used to render the route.
+ * The name of the associated component.
*/
componentName: string;
};
/**
- * Analytics domain covering component extensions.
+ * Allow arbitrary scalar values as context attributes too.
*/
-export type ComponentAnalyticsDomain = {
- /**
- * The plugin that exposed the component.
- */
- pluginId: string;
-
- /**
- * The name of the component.
- */
- componentName: string;
-};
-
-/**
- * Allow arbitrary scalar values as domain attributes too.
- */
-export type AnyAnalyticsDomain = {
+type AnyAnalyticsContext = {
[param in string]: string | boolean | number | undefined;
};
/**
- * Common analytics domain attributes.
+ * Analytics context envelope.
*/
-export type AnalyticsDomainValue = Partial<
- RoutableAnalyticsDomain & ComponentAnalyticsDomain & AnyAnalyticsDomain
+export type AnalyticsContextValue = Partial<
+ CommonAnalyticsContext & AnyAnalyticsContext
>;
diff --git a/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx b/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx
index d8d6706a9e..ce27602e6c 100644
--- a/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx
+++ b/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx
@@ -41,17 +41,20 @@ describe('useAnalytics', () => {
mocked(useApi).mockReturnValue({ captureEvent });
// Calling the captureEvent method of the underlying implementation should
- // pass along the given event as well as the default domain.
+ // pass along the given event as well as the default context.
const { result } = renderHook(() => useAnalytics());
- result.current.captureEvent('a verb', 'a noun', 42, { some: 'value' });
- expect(captureEvent).toHaveBeenCalledWith({
- verb: 'a verb',
- noun: 'a noun',
+ result.current.captureEvent('an action', 'a subject', {
value: 42,
- context: {
+ attributes: { some: 'value' },
+ });
+ expect(captureEvent).toHaveBeenCalledWith({
+ action: 'an action',
+ subject: 'a subject',
+ value: 42,
+ attributes: {
some: 'value',
},
- domain: {
+ context: {
componentName: 'App',
pluginId: 'root',
routeRef: 'unknown',
diff --git a/packages/core-plugin-api/src/analytics/useAnalytics.tsx b/packages/core-plugin-api/src/analytics/useAnalytics.tsx
index b120689e2e..8b104e347c 100644
--- a/packages/core-plugin-api/src/analytics/useAnalytics.tsx
+++ b/packages/core-plugin-api/src/analytics/useAnalytics.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useAnalyticsDomain } from './AnalyticsDomain';
+import { useAnalyticsContext } from './AnalyticsContext';
import {
analyticsApiRef,
AnalyticsTracker,
@@ -23,16 +23,16 @@ import { useApi } from '../apis';
function useTracker(): AnalyticsTracker {
const analyticsApi = useApi(analyticsApiRef);
- const domain = useAnalyticsDomain();
+ const context = useAnalyticsContext();
return {
- captureEvent: (verb, noun, value, context) => {
+ captureEvent: (action, subject, { value, attributes } = {}) => {
try {
analyticsApi.captureEvent({
- verb,
- noun,
+ action,
+ subject,
value,
+ attributes,
context,
- domain,
});
} catch (e) {
// eslint-disable-next-line no-console
diff --git a/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts b/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts
index 4601077835..43e35607a3 100644
--- a/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts
+++ b/packages/core-plugin-api/src/apis/definitions/AnalyticsApi.ts
@@ -15,20 +15,18 @@
*/
import { ApiRef, createApiRef } from '../system';
-import { AnalyticsDomainValue } from '../../analytics/types';
+import { AnalyticsContextValue } from '../../analytics/types';
/**
* Represents an event worth tracking in an analytics system that could inform
* how users of a Backstage instance are using its features.
- *
- * Note that attributes about the Backstage user or about the plugin tracking
- * the event are inferred and captured separately on the Analytics Domain and
- * do not need to be passed on the event itself.
*/
export type AnalyticsEvent = {
/**
* A string that identifies the event being tracked by the type of action the
- * event represents. Examples include:
+ * event represents. Be careful not to encode extra metadata in this string
+ * that should instead be placed in the Analytics Context or attributes.
+ * Examples include:
*
* - view
* - click
@@ -37,18 +35,18 @@ export type AnalyticsEvent = {
* - hover
* - scroll
*/
- verb: string;
+ action: string;
/**
- * A string that uniquely identifies the object that the verb or action is
- * being taken on. Examples include:
+ * A string that uniquely identifies the object that the action is being
+ * taken on. Examples include:
*
* - The path of the page viewed
* - The url of the link clicked
* - The value that was filtered by
* - The text that was searched for
*/
- noun: string;
+ subject: string;
/**
* An optional numeric value relevant to the event that could be aggregated
@@ -57,33 +55,29 @@ export type AnalyticsEvent = {
* - The index or position of the clicked element in an ordered list
* - The percentage of an element that has been scrolled through
* - The amount of time that has elapsed since a fixed point
+ * - A satisfaction score on a fixed scale
*/
value?: number;
/**
- * Optional context with any additional dimensions or metrics that could be
- * forwarded on to analytics systems.
+ * Optional, additional attributes (representing dimensions or metrics)
+ * specific to the event that could be forwarded on to analytics systems.
*/
- context?: AnalyticsEventContext;
-};
+ attributes?: AnalyticsEventAttributes;
-/**
- * An analytics event combined with domain attributes.
- */
-export type DomainDecoratedAnalyticsEvent = AnalyticsEvent & {
/**
- * Domain metadata relating to where the event was captured and by whom. This
- * could include information about the route, plugin, or component in which
- * an event was captured.
+ * Contextual metadata relating to where the event was captured and by whom.
+ * This could include information about the route, plugin, or extension in
+ * which an event was captured.
*/
- domain: AnalyticsDomainValue;
+ context: AnalyticsContextValue;
};
/**
* A structure allowing other arbitrary metadata to be provided by analytics
* event emitters.
*/
-export type AnalyticsEventContext = {
+export type AnalyticsEventAttributes = {
[attribute in string]: string | boolean | number;
};
@@ -93,10 +87,12 @@ export type AnalyticsEventContext = {
*/
export type AnalyticsTracker = {
captureEvent: (
- verb: string,
- noun: string,
- value?: number,
- context?: AnalyticsEventContext,
+ action: string,
+ subject: string,
+ options?: {
+ value?: number;
+ attributes?: AnalyticsEventAttributes;
+ },
) => void;
};
@@ -112,7 +108,7 @@ export type AnalyticsApi = {
* Primary event handler responsible for compiling and forwarding events to
* an analytics system.
*/
- captureEvent(event: DomainDecoratedAnalyticsEvent): void;
+ captureEvent(event: AnalyticsEvent): void;
};
export const analyticsApiRef: ApiRef = createApiRef({
diff --git a/packages/core-plugin-api/src/extensions/extensions.test.tsx b/packages/core-plugin-api/src/extensions/extensions.test.tsx
index 422b346a2c..42a2bf5e66 100644
--- a/packages/core-plugin-api/src/extensions/extensions.test.tsx
+++ b/packages/core-plugin-api/src/extensions/extensions.test.tsx
@@ -17,7 +17,7 @@
import { withLogCollector } from '@backstage/test-utils-core';
import { render, screen } from '@testing-library/react';
import React from 'react';
-import { useAnalyticsDomain } from '../analytics/AnalyticsDomain';
+import { useAnalyticsContext } from '../analytics/AnalyticsContext';
import { useApp, ErrorBoundaryFallbackProps } from '../app';
import { createPlugin } from '../plugin';
import { createRouteRef } from '../routing';
@@ -109,19 +109,19 @@ describe('extensions', () => {
expect(errors[0]).toMatch('Test error');
});
- it('should wrap extended component with analytics domain', async () => {
- const DomainSpyExtension = plugin.provide(
+ it('should wrap extended component with analytics context', async () => {
+ const AnalyticsSpyExtension = plugin.provide(
createReactExtension({
- name: 'DomainSpy',
+ name: 'AnalyticsSpy',
component: {
sync: () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
- const domain = useAnalyticsDomain();
+ const context = useAnalyticsContext();
return (
<>
-