Skip to content

Commit 9f97233

Browse files
fralongoFrancesco Longo
andauthored
feat: Add analytics metadata to FileInput component (#3561)
Co-authored-by: Francesco Longo <[email protected]>
1 parent d52c5fb commit 9f97233

File tree

4 files changed

+148
-2
lines changed

4 files changed

+148
-2
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React from 'react';
4+
import { render } from '@testing-library/react';
5+
6+
import {
7+
activateAnalyticsMetadata,
8+
GeneratedAnalyticsMetadataFragment,
9+
} from '@cloudscape-design/component-toolkit/internal/analytics-metadata';
10+
import { getGeneratedAnalyticsMetadata } from '@cloudscape-design/component-toolkit/internal/analytics-metadata/utils';
11+
12+
import FileInput, { FileInputProps } from '../../../lib/components/file-input';
13+
import { GeneratedAnalyticsMetadataFileInputClick } from '../../../lib/components/file-input/analytics-metadata/interfaces';
14+
import InternalFileInput from '../../../lib/components/file-input/internal';
15+
import createWrapper from '../../../lib/components/test-utils/dom';
16+
import { validateComponentNameAndLabels } from '../../internal/__tests__/analytics-metadata-test-utils';
17+
18+
import labels from '../../../lib/components/button/analytics-metadata/styles.css.js';
19+
20+
function renderFileInput(props: Partial<FileInputProps> = {}) {
21+
const renderResult = render(<FileInput value={[]} onChange={() => {}} {...props} />);
22+
return createWrapper(renderResult.container).findFileInput()!.findTrigger();
23+
}
24+
25+
const getMetadata = (label: string) => {
26+
const analyticsAction: GeneratedAnalyticsMetadataFileInputClick = {
27+
action: 'click',
28+
detail: { label },
29+
};
30+
const metadata: GeneratedAnalyticsMetadataFragment = {
31+
...analyticsAction,
32+
contexts: [
33+
{
34+
type: 'component',
35+
detail: {
36+
name: 'awsui.FileInput',
37+
label,
38+
},
39+
},
40+
],
41+
};
42+
return metadata;
43+
};
44+
45+
beforeAll(() => {
46+
activateAnalyticsMetadata(true);
47+
});
48+
describe('FileInput renders correct analytics metadata', () => {
49+
describe('with button variant', () => {
50+
test('and children', () => {
51+
const wrapper = renderFileInput({ variant: 'button', children: 'Upload files' });
52+
validateComponentNameAndLabels(wrapper.getElement(), labels);
53+
expect(getGeneratedAnalyticsMetadata(wrapper.getElement())).toEqual(getMetadata('Upload files'));
54+
});
55+
test('and aria-label', () => {
56+
const wrapper = renderFileInput({ variant: 'button', ariaLabel: 'Upload files' });
57+
validateComponentNameAndLabels(wrapper.getElement(), labels);
58+
expect(getGeneratedAnalyticsMetadata(wrapper.getElement())).toEqual(getMetadata('Upload files'));
59+
});
60+
test('and both children and aria-label', () => {
61+
const wrapper = renderFileInput({ variant: 'button', children: 'Upload files', ariaLabel: 'Another label' });
62+
validateComponentNameAndLabels(wrapper.getElement(), labels);
63+
expect(getGeneratedAnalyticsMetadata(wrapper.getElement())).toEqual(getMetadata('Upload files'));
64+
});
65+
});
66+
describe('with icon variant', () => {
67+
test('and children', () => {
68+
const wrapper = renderFileInput({ variant: 'icon', children: 'Upload files' });
69+
validateComponentNameAndLabels(wrapper.getElement(), labels);
70+
expect(getGeneratedAnalyticsMetadata(wrapper.getElement())).toEqual(getMetadata('Upload files'));
71+
});
72+
test('and aria-label', () => {
73+
const wrapper = renderFileInput({ variant: 'icon', ariaLabel: 'Upload files' });
74+
validateComponentNameAndLabels(wrapper.getElement(), labels);
75+
expect(getGeneratedAnalyticsMetadata(wrapper.getElement())).toEqual(getMetadata('Upload files'));
76+
});
77+
test('and both children and aria-label', () => {
78+
const wrapper = renderFileInput({ variant: 'icon', children: 'Upload files', ariaLabel: 'Another label' });
79+
validateComponentNameAndLabels(wrapper.getElement(), labels);
80+
expect(getGeneratedAnalyticsMetadata(wrapper.getElement())).toEqual(getMetadata('Another label'));
81+
});
82+
});
83+
});
84+
describe('Internal FileInput', () => {
85+
test('does not render "component" metadata', () => {
86+
const renderResult = render(
87+
<InternalFileInput value={[]} onChange={() => {}}>
88+
inline button text
89+
</InternalFileInput>
90+
);
91+
const wrapper = createWrapper(renderResult.container).findFileInput()!.findTrigger();
92+
validateComponentNameAndLabels(wrapper.getElement(), labels);
93+
expect(getGeneratedAnalyticsMetadata(wrapper.getElement())).toEqual({
94+
action: 'click',
95+
detail: {
96+
label: 'inline button text',
97+
},
98+
});
99+
});
100+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
export interface GeneratedAnalyticsMetadataFileInputClick {
5+
action: 'click';
6+
detail: {
7+
label: string;
8+
};
9+
}
10+
11+
export interface GeneratedAnalyticsMetadataFileInputComponent {
12+
name: 'awsui.FileInput';
13+
label: string;
14+
}

src/file-input/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,16 @@ const FileInput = React.forwardRef(
1717
variant,
1818
},
1919
});
20-
return <InternalFileInput multiple={multiple} variant={variant} {...props} {...baseComponentProps} ref={ref} />;
20+
return (
21+
<InternalFileInput
22+
multiple={multiple}
23+
variant={variant}
24+
{...props}
25+
{...baseComponentProps}
26+
ref={ref}
27+
__injectAnalyticsComponentMetadata={true}
28+
/>
29+
);
2130
}
2231
);
2332

src/file-input/internal.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import React, { ChangeEvent, Ref, useEffect, useRef, useState } from 'react';
55
import clsx from 'clsx';
66

77
import { useMergeRefs, warnOnce } from '@cloudscape-design/component-toolkit/internal';
8+
import {
9+
GeneratedAnalyticsMetadataFragment,
10+
getAnalyticsMetadataAttribute,
11+
} from '@cloudscape-design/component-toolkit/internal/analytics-metadata';
812

913
import InternalButton from '../button/internal';
1014
import { useFormFieldContext } from '../contexts/form-field';
@@ -17,13 +21,15 @@ import useForwardFocus from '../internal/hooks/forward-focus';
1721
import { InternalBaseComponentProps } from '../internal/hooks/use-base-component/index.js';
1822
import { useUniqueId } from '../internal/hooks/use-unique-id';
1923
import { joinStrings } from '../internal/utils/strings';
24+
import { GeneratedAnalyticsMetadataFileInputComponent } from './analytics-metadata/interfaces';
2025
import { FileInputProps } from './interfaces';
2126

2227
import styles from './styles.css.js';
2328

2429
interface InternalFileInputProps {
2530
__inputClassName?: string;
2631
__inputNativeAttributes?: React.InputHTMLAttributes<HTMLInputElement> | Record<`data-${string}`, string>;
32+
__injectAnalyticsComponentMetadata?: boolean;
2733
}
2834

2935
const InternalFileInput = React.forwardRef(
@@ -40,6 +46,7 @@ const InternalFileInput = React.forwardRef(
4046
__internalRootRef = null,
4147
__inputClassName,
4248
__inputNativeAttributes,
49+
__injectAnalyticsComponentMetadata,
4350
...restProps
4451
}: FileInputProps & InternalBaseComponentProps & InternalFileInputProps,
4552
ref: Ref<FileInputProps.Ref>
@@ -104,8 +111,24 @@ const InternalFileInput = React.forwardRef(
104111

105112
const { tabIndex } = useSingleTabStopNavigation(uploadInputRef);
106113

114+
const analyticsLabel = variant === 'button' && children ? 'button' : 'input';
115+
const componentAnalyticsMetadata: GeneratedAnalyticsMetadataFileInputComponent = {
116+
name: 'awsui.FileInput',
117+
label: analyticsLabel,
118+
};
119+
120+
const analyticsMetadata: GeneratedAnalyticsMetadataFragment = { detail: { label: analyticsLabel } };
121+
if (__injectAnalyticsComponentMetadata) {
122+
analyticsMetadata.component = componentAnalyticsMetadata;
123+
}
124+
107125
return (
108-
<div {...baseProps} ref={mergedRef} className={clsx(baseProps.className, styles.root)}>
126+
<div
127+
{...baseProps}
128+
ref={mergedRef}
129+
className={clsx(baseProps.className, styles.root)}
130+
{...getAnalyticsMetadataAttribute(analyticsMetadata)}
131+
>
109132
{/* This is the actual interactive and accessible file-upload element. */}
110133
{/* It is visually hidden to achieve the desired UX design. */}
111134
<input

0 commit comments

Comments
 (0)