Skip to content

Commit

Permalink
chore: Attach task interaction id to table instance (#2778)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeldowseza authored Sep 27, 2024
1 parent 1a3ef83 commit 296fff3
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 9 deletions.
1 change: 1 addition & 0 deletions src/internal/analytics/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ export interface IPerformanceMetrics {

export interface ComponentMountedProps {
componentName: string;
taskInteractionId?: string;
details: Record<string, string | boolean | number | undefined>;
}
export type ComponentMountedMethod = (props: ComponentMountedProps) => string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import React, { useRef } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { render } from '@testing-library/react';

import { setComponentMetrics } from '../../../analytics';
import { useComponentAnalytics } from '../index';

function Demo() {
useComponentAnalytics('demo', () => ({
const ref = useRef<HTMLDivElement>(null);
const { attributes } = useComponentAnalytics('demo', ref, () => ({
key: 'value',
}));

return <div />;
return <div {...attributes} ref={ref} data-testid="element" />;
}

describe('useComponentAnalytics', () => {
Expand All @@ -28,6 +30,33 @@ describe('useComponentAnalytics', () => {
render(<Demo />);

expect(componentMounted).toHaveBeenCalledTimes(1);
expect(componentMounted).toHaveBeenCalledWith({ componentName: 'demo', details: { key: 'value' } });
expect(componentMounted).toHaveBeenCalledWith({
taskInteractionId: expect.any(String),
componentName: 'demo',
details: { key: 'value' },
});
});

test('data attribute should be present after the first render', () => {
const { getByTestId } = render(<Demo />);

expect(getByTestId('element')).toHaveAttribute('data-analytics-task-interaction-id');
});

test('data attribute should be present after re-rendering', () => {
const { getByTestId, rerender } = render(<Demo />);
const attributeValueBefore = getByTestId('element').getAttribute('data-analytics-task-interaction-id');
rerender(<Demo />);

expect(getByTestId('element')).toHaveAttribute('data-analytics-task-interaction-id');

const attributeValueAfter = getByTestId('element').getAttribute('data-analytics-task-interaction-id');
expect(attributeValueAfter).toBe(attributeValueBefore);
});

test('should not render the attribute during server-side rendering', () => {
const markup = renderToStaticMarkup(<Demo />);

expect(markup).toBe('<div data-testid="element"></div>');
});
});
30 changes: 26 additions & 4 deletions src/internal/hooks/use-component-analytics/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,39 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { useEffect } from 'react';
import { useEffect, useRef } from 'react';

import { ComponentMetrics } from '../../analytics';
import { useRandomId } from '../use-unique-id';

function useTaskInteractionAttribute(elementRef: React.RefObject<HTMLElement>, value: string) {
const attributeName = 'data-analytics-task-interaction-id';

const attributeValueRef = useRef<string | undefined>();

useEffect(() => {
// With this effect, we apply the attribute only on the client, to avoid hydration errors.
attributeValueRef.current = value;
elementRef.current?.setAttribute(attributeName, value);
}, [value, elementRef]);

return {
[attributeName]: attributeValueRef.current,
};
}

export function useComponentAnalytics(
componentName: string,
elementRef: React.RefObject<HTMLElement>,
getDetails: () => Record<string, string | boolean | number | undefined>
) {
useEffect(() => {
ComponentMetrics.componentMounted({ componentName, details: getDetails() });
const taskInteractionId = useRandomId();
const attributes = useTaskInteractionAttribute(elementRef, taskInteractionId);

useEffect(() => {
ComponentMetrics.componentMounted({ taskInteractionId, componentName, details: getDetails() });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [taskInteractionId]);

return { taskInteractionId, attributes };
}
3 changes: 2 additions & 1 deletion src/table/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ const InternalTable = React.forwardRef(
);

const analyticsMetadata = getAnalyticsMetadataProps(rest);
useComponentAnalytics('table', () => ({
const { attributes: componentAnalyticsAttributes } = useComponentAnalytics('table', tableRefObject, () => ({
variant,
flowType: rest.analyticsMetadata?.flowType,
instanceIdentifier: analyticsMetadata?.instanceIdentifier,
Expand Down Expand Up @@ -488,6 +488,7 @@ const InternalTable = React.forwardRef(
>
<table
{...performanceMarkAttributes}
{...componentAnalyticsAttributes}
ref={tableRef}
className={clsx(
styles.table,
Expand Down

0 comments on commit 296fff3

Please sign in to comment.