Skip to content

Commit

Permalink
feat: Add analytics metadata to Pagination (#2514)
Browse files Browse the repository at this point in the history
  • Loading branch information
fralongo committed Jul 25, 2024
1 parent 4e161d0 commit 302de2d
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 5 deletions.
158 changes: 158 additions & 0 deletions src/pagination/__tests__/analytics-metadata.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { render } from '@testing-library/react';

import {
activateAnalyticsMetadata,
GeneratedAnalyticsMetadataFragment,
} from '@cloudscape-design/component-toolkit/internal/analytics-metadata';
import { getGeneratedAnalyticsMetadata } from '@cloudscape-design/component-toolkit/internal/analytics-metadata/utils';

import Pagination, { PaginationProps } from '../../../lib/components/pagination';
import InternalPagination from '../../../lib/components/pagination/internal';
import createWrapper, { PaginationWrapper } from '../../../lib/components/test-utils/dom';
import { validateComponentNameAndLabels } from '../../internal/__tests__/analytics-metadata-test-utils';

const ariaLabels = {
paginationLabel: 'Pagination label',
nextPageLabel: 'Next page',
previousPageLabel: 'Previous page',
pageLabel: (pageNumber: number) => `Page ${pageNumber}`,
};

function renderPagination(props: PaginationProps) {
const renderResult = render(<Pagination {...props} ariaLabels={ariaLabels} />);
return createWrapper(renderResult.container).findPagination()!;
}

const getMetadata = (label: string, props: PaginationProps, eventDetail?: Record<string, string>) => {
const metadata: GeneratedAnalyticsMetadataFragment = {
contexts: [
{
type: 'component',
detail: {
name: 'awsui.Pagination',
label,
properties: {
openEnd: `${!!props.openEnd}`,
pagesCount: `${props.pagesCount || ''}`,
currentPageIndex: `${props.currentPageIndex}`,
},
},
},
],
};
if (eventDetail) {
metadata.action = 'click';
metadata.detail = eventDetail;
}
return metadata;
};

const testNext = (wrapper: PaginationWrapper, props: PaginationProps, disabled?: boolean) => {
const nextButton = wrapper.findNextPageButton()!.getElement();
validateComponentNameAndLabels(nextButton, {});
expect(getGeneratedAnalyticsMetadata(nextButton)).toEqual(
getMetadata(
ariaLabels.paginationLabel,
props,
disabled ? undefined : { label: ariaLabels.nextPageLabel, position: 'next' }
)
);
};
const testPrevious = (wrapper: PaginationWrapper, props: PaginationProps, disabled?: boolean) => {
const previousButton = wrapper.findPreviousPageButton()!.getElement();
validateComponentNameAndLabels(previousButton, {});
expect(getGeneratedAnalyticsMetadata(previousButton)).toEqual(
getMetadata(
ariaLabels.paginationLabel,
props,
disabled ? undefined : { label: ariaLabels.previousPageLabel, position: 'prev' }
)
);
};
const testButton = (wrapper: PaginationWrapper, props: PaginationProps, page: number, disabled?: boolean) => {
const pageButton = wrapper.findPageNumbers()![2]!.getElement();
validateComponentNameAndLabels(pageButton, {});
expect(getGeneratedAnalyticsMetadata(pageButton)).toEqual(
getMetadata(
ariaLabels.paginationLabel,
props,
disabled ? undefined : { label: ariaLabels.pageLabel(page), position: `${page}` }
)
);
};

beforeAll(() => {
activateAnalyticsMetadata(true);
});
describe('Pagination renders correct analytics metadata', () => {
test('enabled', () => {
const paginationProps: PaginationProps = {
pagesCount: 300,
currentPageIndex: 20,
};
const wrapper = renderPagination(paginationProps);

testNext(wrapper, paginationProps);
testPrevious(wrapper, paginationProps);
testButton(wrapper, paginationProps, 19);
});
test('disabled', () => {
const paginationProps: PaginationProps = {
pagesCount: 300,
currentPageIndex: 20,
disabled: true,
};
const wrapper = renderPagination(paginationProps);

testNext(wrapper, paginationProps, true);
testPrevious(wrapper, paginationProps, true);
testButton(wrapper, paginationProps, 19, true);
});

test('first page', () => {
const paginationProps: PaginationProps = {
pagesCount: 300,
currentPageIndex: 1,
};
const wrapper = renderPagination(paginationProps);

testNext(wrapper, paginationProps);
testPrevious(wrapper, paginationProps, true);
testButton(wrapper, paginationProps, 3);
});

test('last page', () => {
const paginationProps: PaginationProps = {
pagesCount: 300,
currentPageIndex: 300,
};
const wrapper = renderPagination(paginationProps);

testNext(wrapper, paginationProps, true);
testPrevious(wrapper, paginationProps);
testButton(wrapper, paginationProps, 295);
});

test('last page and openEnd', () => {
const paginationProps: PaginationProps = {
pagesCount: 300,
currentPageIndex: 300,
openEnd: true,
};
const wrapper = renderPagination(paginationProps);

testNext(wrapper, paginationProps);
testPrevious(wrapper, paginationProps);
testButton(wrapper, paginationProps, 296);
});
});

test('Internal Pagination does not render "component" metadata', () => {
const renderResult = render(<InternalPagination pagesCount={300} currentPageIndex={2} />);
const wrapper = createWrapper(renderResult.container).findPagination()!;
validateComponentNameAndLabels(wrapper.getElement(), {});
expect(getGeneratedAnalyticsMetadata(wrapper.getElement())).toEqual({});
});
20 changes: 20 additions & 0 deletions src/pagination/analytics-metadata/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

export interface GeneratedAnalyticsMetadataPaginationClick {
action: 'click';
detail: {
label: string;
position?: string;
};
}

export interface GeneratedAnalyticsMetadataPaginationComponent {
name: 'awsui.Pagination';
label: string;
properties: {
openEnd: string;
pagesCount: string;
currentPageIndex: string;
};
}
21 changes: 20 additions & 1 deletion src/pagination/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,35 @@
// SPDX-License-Identifier: Apache-2.0
import React from 'react';

import { getAnalyticsMetadataAttribute } from '@cloudscape-design/component-toolkit/internal/analytics-metadata';

import useBaseComponent from '../internal/hooks/use-base-component';
import { applyDisplayName } from '../internal/utils/apply-display-name';
import { GeneratedAnalyticsMetadataPaginationComponent } from './analytics-metadata/interfaces';
import { PaginationProps } from './interfaces';
import InternalPagination from './internal';

export { PaginationProps };

export default function Pagination(props: PaginationProps) {
const baseComponentProps = useBaseComponent('Pagination', { props: { openEnd: props.openEnd } });
return <InternalPagination {...props} {...baseComponentProps} />;
return (
<InternalPagination
{...props}
{...baseComponentProps}
{...getAnalyticsMetadataAttribute({
component: {
name: 'awsui.Pagination',
label: '',
properties: {
openEnd: `${!!props.openEnd}`,
pagesCount: `${props.pagesCount || ''}`,
currentPageIndex: `${props.currentPageIndex}`,
},
} as GeneratedAnalyticsMetadataPaginationComponent,
})}
/>
);
}

applyDisplayName(Pagination, 'Pagination');
51 changes: 47 additions & 4 deletions src/pagination/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
import React from 'react';
import clsx from 'clsx';

import {
copyAnalyticsMetadataAttribute,
getAnalyticsMetadataAttribute,
} from '@cloudscape-design/component-toolkit/internal/analytics-metadata';

import { useInternalI18n } from '../i18n/context';
import InternalIcon from '../icon/internal';
import { getBaseProps } from '../internal/base-component';
import { fireNonCancelableEvent } from '../internal/events';
import { InternalBaseComponentProps } from '../internal/hooks/use-base-component';
import { GeneratedAnalyticsMetadataPaginationClick } from './analytics-metadata/interfaces';
import { PaginationProps } from './interfaces';
import { getPaginationState, range } from './utils';

Expand Down Expand Up @@ -38,13 +44,14 @@ function PageButton({
isCurrent = false,
children,
onClick,
...rest
}: PageButtonProps) {
function handleClick(event: React.MouseEvent) {
event.preventDefault();
onClick(pageIndex);
}
return (
<li className={styles['page-item']}>
<li className={styles['page-item']} {...copyAnalyticsMetadataAttribute(rest)}>
<button
className={clsx(
className,
Expand All @@ -57,6 +64,14 @@ function PageButton({
disabled={disabled}
onClick={handleClick}
aria-current={isCurrent}
{...(disabled
? {}
: getAnalyticsMetadataAttribute({
action: 'click',
detail: {
label: '',
},
} as GeneratedAnalyticsMetadataPaginationClick))}
>
{children}
</button>
Expand All @@ -66,7 +81,18 @@ function PageButton({

function PageNumber({ pageIndex, ...rest }: PageButtonProps) {
return (
<PageButton className={styles['page-number']} pageIndex={pageIndex} {...rest}>
<PageButton
className={styles['page-number']}
pageIndex={pageIndex}
{...rest}
{...(rest.disabled
? {}
: getAnalyticsMetadataAttribute({
detail: {
position: `${pageIndex}`,
},
}))}
>
{pageIndex}
</PageButton>
);
Expand Down Expand Up @@ -119,6 +145,9 @@ export default function InternalPagination({
fireNonCancelableEvent(onChange, { currentPageIndex: requestedPageIndex });
}

const previousButtonDisabled = disabled || currentPageIndex === 1;
const nextButtonDisabled = disabled || (!openEnd && (pagesCount === 0 || currentPageIndex === pagesCount));

return (
<ul
aria-label={paginationLabel}
Expand All @@ -130,8 +159,15 @@ export default function InternalPagination({
className={styles.arrow}
pageIndex={currentPageIndex - 1}
ariaLabel={previousPageLabel ?? defaultAriaLabels.nextPageLabel}
disabled={disabled || currentPageIndex === 1}
disabled={previousButtonDisabled}
onClick={handlePrevPageClick}
{...(previousButtonDisabled
? {}
: getAnalyticsMetadataAttribute({
detail: {
position: 'prev',
},
}))}
>
<InternalIcon name="angle-left" variant={disabled ? 'disabled' : 'normal'} />
</PageButton>
Expand Down Expand Up @@ -167,8 +203,15 @@ export default function InternalPagination({
className={styles.arrow}
pageIndex={currentPageIndex + 1}
ariaLabel={nextPageLabel ?? defaultAriaLabels.nextPageLabel}
disabled={disabled || (!openEnd && (pagesCount === 0 || currentPageIndex === pagesCount))}
disabled={nextButtonDisabled}
onClick={handleNextPageClick}
{...(nextButtonDisabled
? {}
: getAnalyticsMetadataAttribute({
detail: {
position: 'next',
},
}))}
>
<InternalIcon name="angle-right" variant={disabled ? 'disabled' : 'normal'} />
</PageButton>
Expand Down

0 comments on commit 302de2d

Please sign in to comment.