Skip to content

Commit 2295fdb

Browse files
fralongoFrancesco Longo
andauthored
feat: Add analytics metadata to input and textarea components (#3671)
Co-authored-by: Francesco Longo <[email protected]>
1 parent 894be9b commit 2295fdb

File tree

7 files changed

+216
-17
lines changed

7 files changed

+216
-17
lines changed

src/input/__tests__/analytics-metadata.test.tsx

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,101 @@
33
import React from 'react';
44
import { render } from '@testing-library/react';
55

6-
import { activateAnalyticsMetadata } from '@cloudscape-design/component-toolkit/internal/analytics-metadata';
6+
import {
7+
activateAnalyticsMetadata,
8+
GeneratedAnalyticsMetadataFragment,
9+
} from '@cloudscape-design/component-toolkit/internal/analytics-metadata';
710
import { getGeneratedAnalyticsMetadata } from '@cloudscape-design/component-toolkit/internal/analytics-metadata/utils';
811

12+
import FormField from '../../../lib/components/form-field';
913
import Input from '../../../lib/components/input';
1014
import InternalInput from '../../../lib/components/input/internal';
1115
import createWrapper from '../../../lib/components/test-utils/dom';
1216

1317
import styles from '../../../lib/components/input/styles.css.js';
1418

19+
const getComponentMetadata = (label: string, value: string) => {
20+
const metadata: GeneratedAnalyticsMetadataFragment = {
21+
contexts: [
22+
{
23+
type: 'component',
24+
detail: {
25+
name: 'awsui.Input',
26+
label,
27+
properties: {
28+
value,
29+
},
30+
},
31+
},
32+
],
33+
};
34+
return metadata;
35+
};
36+
1537
beforeAll(() => {
1638
activateAnalyticsMetadata(true);
1739
});
1840
describe('Input renders correct analytics metadata', () => {
19-
test('on the clear button', () => {
20-
const renderResult = render(<Input value="value" onChange={() => {}} type="search" clearAriaLabel="clear" />);
21-
const clearInputButton = createWrapper(renderResult.container).findInput()!.findClearButton()!.getElement();
22-
expect(getGeneratedAnalyticsMetadata(clearInputButton)).toEqual({
23-
action: 'clearInput',
24-
detail: {
25-
label: 'clear',
26-
},
41+
describe('on the right button', () => {
42+
test('when it is the clear button', () => {
43+
const renderResult = render(<Input value="value" onChange={() => {}} type="search" clearAriaLabel="clear" />);
44+
const clearInputButton = createWrapper(renderResult.container).findInput()!.findClearButton()!.getElement();
45+
expect(getGeneratedAnalyticsMetadata(clearInputButton)).toEqual({
46+
action: 'clearInput',
47+
detail: {
48+
label: 'clear',
49+
},
50+
...getComponentMetadata('', 'value'),
51+
});
52+
});
53+
test('when it is not the clear button', () => {
54+
const renderResult = render(<InternalInput value="value" onChange={() => {}} __rightIcon="settings" />);
55+
const rightIconButton = createWrapper(renderResult.container)
56+
.findByClassName(styles['input-icon-right'])!
57+
.getElement();
58+
expect(getGeneratedAnalyticsMetadata(rightIconButton)).toEqual({});
2759
});
2860
});
29-
test('without the clear button', () => {
30-
const renderResult = render(<InternalInput value="value" onChange={() => {}} __rightIcon="settings" />);
31-
const rightIconButton = createWrapper(renderResult.container)
32-
.findByClassName(styles['input-icon-right'])!
33-
.getElement();
34-
expect(getGeneratedAnalyticsMetadata(rightIconButton)).toEqual({});
61+
describe('on the component', () => {
62+
test('with aria label', () => {
63+
const renderResult = render(<Input value="a" onChange={() => {}} ariaLabel="label" />);
64+
const componentElement = createWrapper(renderResult.container).findInput()!.getElement();
65+
expect(getGeneratedAnalyticsMetadata(componentElement)).toEqual(getComponentMetadata('label', 'a'));
66+
});
67+
test('with empty value', () => {
68+
const renderResult = render(<Input value="" onChange={() => {}} ariaLabel="label" />);
69+
const componentElement = createWrapper(renderResult.container).findInput()!.getElement();
70+
expect(getGeneratedAnalyticsMetadata(componentElement)).toEqual(getComponentMetadata('label', ''));
71+
});
72+
test('within a form field', () => {
73+
const formFieldLabel = 'form-field-label';
74+
const renderResult = render(
75+
<FormField label={formFieldLabel}>
76+
<Input value="a" onChange={() => {}} />
77+
</FormField>
78+
);
79+
const componentElement = createWrapper(renderResult.container).findInput()!.getElement();
80+
expect(getGeneratedAnalyticsMetadata(componentElement)).toEqual({
81+
contexts: [
82+
{
83+
type: 'component',
84+
detail: {
85+
name: 'awsui.Input',
86+
label: formFieldLabel,
87+
properties: {
88+
value: 'a',
89+
},
90+
},
91+
},
92+
{
93+
type: 'component',
94+
detail: {
95+
name: 'awsui.FormField',
96+
label: formFieldLabel,
97+
},
98+
},
99+
],
100+
});
101+
});
35102
});
36103
});

src/input/analytics-metadata/interfaces.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,11 @@ export interface GeneratedAnalyticsMetadataInputClearInput {
77
label: string;
88
};
99
}
10+
11+
export interface GeneratedAnalyticsMetadataInputComponent {
12+
name: 'awsui.Input';
13+
label: string;
14+
properties: {
15+
value: string;
16+
};
17+
}

src/input/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ const Input = React.forwardRef(
100100
}}
101101
className={clsx(styles.root, baseProps.className)}
102102
__inheritFormFieldProps={true}
103+
__injectAnalyticsComponentMetadata={true}
103104
/>
104105
);
105106
}

src/input/internal.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import { FormFieldValidationControlProps, useFormFieldContext } from '../interna
1515
import { fireKeyboardEvent, fireNonCancelableEvent, NonCancelableEventHandler } from '../internal/events';
1616
import { InternalBaseComponentProps } from '../internal/hooks/use-base-component';
1717
import { useDebounceCallback } from '../internal/hooks/use-debounce-callback';
18-
import { GeneratedAnalyticsMetadataInputClearInput } from './analytics-metadata/interfaces';
18+
import {
19+
GeneratedAnalyticsMetadataInputClearInput,
20+
GeneratedAnalyticsMetadataInputComponent,
21+
} from './analytics-metadata/interfaces';
1922
import { BaseChangeDetail, BaseInputProps, InputAutoCorrect, InputProps } from './interfaces';
2023
import { convertAutoComplete, useSearchProps } from './utils';
2124

@@ -43,6 +46,7 @@ export interface InternalInputProps
4346
__onBlurWithDetail?: NonCancelableEventHandler<{ relatedTarget: Node | null }>;
4447

4548
__inheritFormFieldProps?: boolean;
49+
__injectAnalyticsComponentMetadata?: boolean;
4650
}
4751

4852
function InternalInput(
@@ -82,6 +86,7 @@ function InternalInput(
8286
__nativeAttributes,
8387
__internalRootRef,
8488
__inheritFormFieldProps,
89+
__injectAnalyticsComponentMetadata,
8590
...rest
8691
}: InternalInputProps,
8792
ref: Ref<HTMLInputElement>
@@ -177,12 +182,23 @@ function InternalInput(
177182
attributes.type = 'text';
178183
}
179184

185+
const componentAnalyticsMetadata: GeneratedAnalyticsMetadataInputComponent = {
186+
name: 'awsui.Input',
187+
label: 'input',
188+
properties: {
189+
value: value || '',
190+
},
191+
};
192+
180193
return (
181194
<div
182195
{...baseProps}
183196
className={clsx(baseProps.className, styles['input-container'])}
184197
ref={__internalRootRef}
185198
dir={type === 'email' ? 'ltr' : undefined}
199+
{...(__injectAnalyticsComponentMetadata
200+
? getAnalyticsMetadataAttribute({ component: componentAnalyticsMetadata })
201+
: {})}
186202
>
187203
{__leftIcon && (
188204
<span onClick={__onLeftIconClick} className={styles['input-icon-left']}>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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 FormField from '../../../lib/components/form-field';
13+
import createWrapper from '../../../lib/components/test-utils/dom';
14+
import Textarea, { TextareaProps } from '../../../lib/components/textarea';
15+
16+
const getComponentMetadata = (label: string, value: string) => {
17+
const metadata: GeneratedAnalyticsMetadataFragment = {
18+
contexts: [
19+
{
20+
type: 'component',
21+
detail: {
22+
name: 'awsui.Textarea',
23+
label,
24+
properties: {
25+
value,
26+
},
27+
},
28+
},
29+
],
30+
};
31+
return metadata;
32+
};
33+
34+
function renderTextarea(props: Partial<TextareaProps>) {
35+
const renderResult = render(<Textarea value="a" onChange={() => {}} {...props} />);
36+
return createWrapper(renderResult.container).findTextarea()!;
37+
}
38+
39+
beforeAll(() => {
40+
activateAnalyticsMetadata(true);
41+
});
42+
describe('Input renders correct analytics metadata', () => {
43+
test('with aria label', () => {
44+
const componentElement = renderTextarea({ ariaLabel: 'label' }).getElement();
45+
expect(getGeneratedAnalyticsMetadata(componentElement)).toEqual(getComponentMetadata('label', 'a'));
46+
});
47+
test('with empty value', () => {
48+
const componentElement = renderTextarea({ ariaLabel: 'label', value: '' }).getElement();
49+
expect(getGeneratedAnalyticsMetadata(componentElement)).toEqual(getComponentMetadata('label', ''));
50+
});
51+
test('within a form field', () => {
52+
const formFieldLabel = 'form-field-label';
53+
const renderResult = render(
54+
<FormField label={formFieldLabel}>
55+
<Textarea value="a" onChange={() => {}} />
56+
</FormField>
57+
);
58+
const componentElement = createWrapper(renderResult.container).findTextarea()!.getElement();
59+
expect(getGeneratedAnalyticsMetadata(componentElement)).toEqual({
60+
contexts: [
61+
{
62+
type: 'component',
63+
detail: {
64+
name: 'awsui.Textarea',
65+
label: formFieldLabel,
66+
properties: {
67+
value: 'a',
68+
},
69+
},
70+
},
71+
{
72+
type: 'component',
73+
detail: {
74+
name: 'awsui.FormField',
75+
label: formFieldLabel,
76+
},
77+
},
78+
],
79+
});
80+
});
81+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
export interface GeneratedAnalyticsMetadataTextareaComponent {
5+
name: 'awsui.Textarea';
6+
label: string;
7+
properties: {
8+
value: string;
9+
};
10+
}

src/textarea/index.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
import React, { Ref, useRef } from 'react';
55
import clsx from 'clsx';
66

7+
import { getAnalyticsMetadataAttribute } from '@cloudscape-design/component-toolkit/internal/analytics-metadata';
8+
79
import { convertAutoComplete } from '../input/utils';
810
import { getBaseProps } from '../internal/base-component';
911
import { useFormFieldContext } from '../internal/context/form-field-context';
1012
import { fireKeyboardEvent, fireNonCancelableEvent } from '../internal/events';
1113
import useForwardFocus from '../internal/hooks/forward-focus';
1214
import useBaseComponent from '../internal/hooks/use-base-component';
1315
import { applyDisplayName } from '../internal/utils/apply-display-name';
16+
import { GeneratedAnalyticsMetadataTextareaComponent } from './analytics-metadata/interfaces';
1417
import { TextareaProps } from './interfaces';
1518

1619
import styles from './styles.css.js';
@@ -89,8 +92,21 @@ const Textarea = React.forwardRef(
8992
attributes.spellCheck = 'false';
9093
}
9194

95+
const componentAnalyticsMetadata: GeneratedAnalyticsMetadataTextareaComponent = {
96+
name: 'awsui.Textarea',
97+
label: 'textarea',
98+
properties: {
99+
value: value || '',
100+
},
101+
};
102+
92103
return (
93-
<span {...baseProps} className={clsx(styles.root, baseProps.className)} ref={__internalRootRef}>
104+
<span
105+
{...baseProps}
106+
className={clsx(styles.root, baseProps.className)}
107+
ref={__internalRootRef}
108+
{...getAnalyticsMetadataAttribute({ component: componentAnalyticsMetadata })}
109+
>
94110
<textarea ref={textareaRef} id={controlId} {...attributes} />
95111
</span>
96112
);

0 commit comments

Comments
 (0)