Skip to content

Commit 82d6b29

Browse files
feat(metadataeditor): add ai extraction for folders (#4080)
* feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders * feat(metadataeditor): add ai extraction for folders --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 174f335 commit 82d6b29

File tree

13 files changed

+214
-16
lines changed

13 files changed

+214
-16
lines changed

i18n/en-US.properties

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,14 @@ boxui.itemDetails.url = URL
12101210
boxui.itemDetails.urlPlaceholder = Enter a valid url
12111211
# Label for comment options menu
12121212
boxui.media.menuButtonArialLabel = Options
1213+
# Description for AI autofill toggle switch
1214+
boxui.metadataInstanceEditor.aiAutofillDescription = Use Box AI to automatically extract metadata values.
1215+
# Learn more link for AI autofill
1216+
boxui.metadataInstanceEditor.aiAutofillLearnMore = Learn more
1217+
# Notice for AI autofill toggle switch
1218+
boxui.metadataInstanceEditor.aiAutofillNotice = Enabling this feature may involve additional charges. Please review our {pricingLink} for more information.
1219+
# Pricing details link for AI autofill
1220+
boxui.metadataInstanceEditor.aiAutofillPricingDetails = pricing details
12131221
# Informational text below collapsible header indicating that all fields for this template are hidden
12141222
boxui.metadataInstanceEditor.allAttributesAreHidden = All attributes in this template have been hidden.
12151223
# Informational text below enable cascade policy toggle switch
@@ -1250,6 +1258,8 @@ boxui.metadataInstanceEditor.customValue = Value
12501258
boxui.metadataInstanceEditor.customValuePlaceholder = e.g. 42
12511259
# Text that shows in a tooltip above the edit pencil button.
12521260
boxui.metadataInstanceEditor.editTooltip = Edit Metadata
1261+
# Label for enable AI autofill toggle switch
1262+
boxui.metadataInstanceEditor.enableAIAutofill = Box AI Autofill
12531263
# Label for enable cascade policy toggle switch
12541264
boxui.metadataInstanceEditor.enableCascadePolicy = Enable Cascade Policy
12551265
# Message for users who may attempt to remove a custom metadata instance for a file. Also non-recoverable

src/features/metadata-instance-editor/CascadePolicy.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
import * as React from 'react';
33
import { FormattedMessage } from 'react-intl';
44

5+
import { InlineNotice } from '@box/blueprint-web';
6+
import BoxAiLogo from '@box/blueprint-web-assets/icons/Logo/BoxAiLogo';
7+
58
import Toggle from '../../components/toggle';
69
import { RadioButton, RadioGroup } from '../../components/radio';
710
import Link from '../../components/link/Link';
@@ -10,22 +13,29 @@ import messages from './messages';
1013
import './CascadePolicy.scss';
1114

1215
const COMMUNITY_LINK = 'https://support.box.com/hc/en-us/articles/360044195873-Cascading-metadata-in-folders';
13-
16+
const AI_LINK = 'https://www.box.com/ai';
17+
const PRICING_LINK = 'https://www.box.com/pricing';
1418
type Props = {
1519
canEdit: boolean,
20+
canUseAIFolderExtraction: boolean,
21+
isAIFolderExtractionEnabled: boolean,
1622
isCascadingEnabled: boolean,
1723
isCascadingOverwritten: boolean,
1824
isCustomMetadata: boolean,
25+
onAIFolderExtractionToggle: (value: boolean) => void,
1926
onCascadeModeChange: (value: boolean) => void,
2027
onCascadeToggle: (value: boolean) => void,
2128
shouldShowCascadeOptions: boolean,
2229
};
2330

2431
const CascadePolicy = ({
2532
canEdit,
33+
canUseAIFolderExtraction,
2634
isCascadingEnabled,
2735
isCascadingOverwritten,
2836
isCustomMetadata,
37+
isAIFolderExtractionEnabled,
38+
onAIFolderExtractionToggle,
2939
onCascadeToggle,
3040
onCascadeModeChange,
3141
shouldShowCascadeOptions,
@@ -57,7 +67,7 @@ const CascadePolicy = ({
5767
<div className="cascade-policy-text">
5868
<FormattedMessage {...messages.applyCascadePolicyText} />
5969
&nbsp;
60-
<Link className="cascade-policy-learnmore-link" href={COMMUNITY_LINK} target="_blank">
70+
<Link className="cascade-policy-link" href={COMMUNITY_LINK} target="_blank">
6171
<FormattedMessage {...messages.cascadePolicyLearnMore} />
6272
</Link>
6373
</div>
@@ -96,6 +106,41 @@ const CascadePolicy = ({
96106
</div>
97107
</div>
98108
)}
109+
{shouldShowCascadeOptions && canUseAIFolderExtraction && (
110+
<div className="metadata-cascade-editor" data-testid="ai-folder-extraction">
111+
<div className="metadata-cascade-enable">
112+
<div>
113+
<BoxAiLogo className="metadata-cascade-ai-logo" width={16} height={16} />
114+
<FormattedMessage tagName="strong" {...messages.enableAIAutofill} />
115+
<Toggle
116+
className="metadata-cascade-toggle"
117+
isOn={isAIFolderExtractionEnabled}
118+
label=""
119+
onChange={e => onAIFolderExtractionToggle(e.target.checked)}
120+
/>
121+
</div>
122+
<div className="cascade-policy-text">
123+
<FormattedMessage {...messages.aiAutofillDescription} />
124+
&nbsp;
125+
<Link className="cascade-policy-link" href={AI_LINK} target="_blank">
126+
<FormattedMessage {...messages.aiAutofillLearnMore} />
127+
</Link>
128+
</div>
129+
<InlineNotice className="metadata-cascade-ai-notice" variant="info">
130+
<FormattedMessage
131+
{...messages.aiAutofillNotice}
132+
values={{
133+
pricingLink: (
134+
<Link className="cascade-policy-link" href={PRICING_LINK} target="_blank">
135+
<FormattedMessage {...messages.aiAutofillPricingDetails} />
136+
</Link>
137+
),
138+
}}
139+
/>
140+
</InlineNotice>
141+
</div>
142+
</div>
143+
)}
99144
</>
100145
) : (
101146
readOnlyState

src/features/metadata-instance-editor/CascadePolicy.scss

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ $cascade-policy-background: #f1e2fd;
4040
.metadata-cascade-enable {
4141
margin: 10px 0;
4242
padding: 14px 15px;
43+
44+
.metadata-cascade-ai-logo {
45+
position: relative;
46+
top: 3px;
47+
margin-right: 3px;
48+
}
49+
50+
.metadata-cascade-ai-notice {
51+
margin-top: 12px;
52+
}
4353
}
4454

4555
.toggle-container.metadata-cascade-toggle {
@@ -52,7 +62,7 @@ $cascade-policy-background: #f1e2fd;
5262
clear: left;
5363
}
5464

55-
.cascade-policy-learnmore-link {
65+
.cascade-policy-link {
5666
color: $bdl-box-blue;
5767
}
5868

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as React from 'react';
2+
3+
import CascadePolicy from './CascadePolicy';
4+
5+
export const withoutAIMetadataExtraction = () => (
6+
<CascadePolicy
7+
canEdit
8+
isCascadingEnabled
9+
onCascadeModeChange={() => {}}
10+
onCascadeToggle={() => {}}
11+
shouldShowCascadeOptions
12+
/>
13+
);
14+
15+
export const withAIMetadataExtraction = () => (
16+
<CascadePolicy
17+
canEdit
18+
canUseAIFolderExtraction
19+
isAIFolderExtractionEnabled
20+
isCascadingEnabled
21+
onAIFolderExtractionToggle={() => {}}
22+
onCascadeModeChange={() => {}}
23+
onCascadeToggle={() => {}}
24+
shouldShowCascadeOptions
25+
/>
26+
);
27+
28+
export default {
29+
title: 'Features/Metadata Instance Editor/CascadePolicy',
30+
component: CascadePolicy,
31+
};

src/features/metadata-instance-editor/Instance.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import './Instance.scss';
4747

4848
type Props = {
4949
canEdit: boolean,
50+
canUseAIFolderExtraction?: boolean,
5051
cascadePolicy?: MetadataCascadePolicy, // eslint-disable-line
5152
data: MetadataFields,
5253
hasError: boolean,
@@ -69,6 +70,7 @@ type Props = {
6970
type State = {
7071
data: Object,
7172
errors: { [string]: React.Node },
73+
isAIFolderExtractionEnabled: boolean,
7274
isBusy: boolean,
7375
isCascadingEnabled: boolean,
7476
isCascadingOverwritten: boolean,
@@ -327,21 +329,28 @@ class Instance extends React.PureComponent<Props, State> {
327329
);
328330
};
329331

332+
onAIFolderExtractionToggle = (value: boolean) => {
333+
this.setState({ isAIFolderExtractionEnabled: value }, this.setDirty);
334+
};
335+
330336
/**
331337
* Returns the state from props
332338
*
333339
* @return {Object} - react state
334340
*/
335341
getState(props: Props): State {
342+
const isCascadingEnabled = this.isCascadingEnabled(props);
343+
336344
return {
337345
data: cloneDeep(props.data),
338346
errors: {},
347+
isAIFolderExtractionEnabled: false,
339348
isBusy: false,
340-
isCascadingEnabled: this.isCascadingEnabled(props),
349+
isCascadingEnabled,
341350
isCascadingOverwritten: false,
342351
isEditing: false,
343352
shouldConfirmRemove: false,
344-
shouldShowCascadeOptions: false,
353+
shouldShowCascadeOptions: isCascadingEnabled,
345354
};
346355
}
347356

@@ -579,12 +588,20 @@ class Instance extends React.PureComponent<Props, State> {
579588
};
580589

581590
render() {
582-
const { cascadePolicy = {}, isDirty, isCascadingPolicyApplicable, isOpen, template }: Props = this.props;
591+
const {
592+
canUseAIFolderExtraction = false,
593+
cascadePolicy = {},
594+
isDirty,
595+
isCascadingPolicyApplicable,
596+
isOpen,
597+
template,
598+
}: Props = this.props;
583599
const { fields = [] } = template;
584600
const {
585601
data,
586602
errors,
587603
isBusy,
604+
isAIFolderExtractionEnabled,
588605
isCascadingEnabled,
589606
shouldConfirmRemove,
590607
shouldShowCascadeOptions,
@@ -629,9 +646,12 @@ class Instance extends React.PureComponent<Props, State> {
629646
{isCascadingPolicyApplicable && (
630647
<CascadePolicy
631648
canEdit={isEditing && !!cascadePolicy.canEdit}
649+
canUseAIFolderExtraction={canUseAIFolderExtraction}
650+
isAIFolderExtractionEnabled={isAIFolderExtractionEnabled}
632651
isCascadingEnabled={isCascadingEnabled}
633652
isCascadingOverwritten={isCascadingOverwritten}
634653
isCustomMetadata={isProperties}
654+
onAIFolderExtractionToggle={this.onAIFolderExtractionToggle}
635655
onCascadeModeChange={this.onCascadeModeChange}
636656
onCascadeToggle={this.onCascadeToggle}
637657
shouldShowCascadeOptions={shouldShowCascadeOptions}

src/features/metadata-instance-editor/Instances.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { MetadataEditor, MetadataCascadingPolicyData } from '../../common/t
66
import type { JSONPatchOperations } from '../../common/types/api';
77

88
type Props = {
9+
canUseAIFolderExtraction?: boolean,
910
editors?: Array<MetadataEditor>,
1011
isCascadingPolicyApplicable?: boolean,
1112
onModification?: (id: string, isDirty: boolean) => void,
@@ -20,6 +21,7 @@ type Props = {
2021
};
2122

2223
const Instances = ({
24+
canUseAIFolderExtraction = false,
2325
isCascadingPolicyApplicable = false,
2426
editors = [],
2527
onModification,
@@ -34,6 +36,7 @@ const Instances = ({
3436
return (
3537
<Instance
3638
canEdit={instance.canEdit}
39+
canUseAIFolderExtraction={canUseAIFolderExtraction}
3740
cascadePolicy={instance.cascadePolicy}
3841
data={instance.data}
3942
hasError={hasError}

src/features/metadata-instance-editor/MetadataInstanceEditor.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import './MetadataInstanceEditor.scss';
1414
type Props = {
1515
blurExceptionClassNames?: Array<string>,
1616
canAdd: boolean,
17+
canUseAIFolderExtraction?: boolean,
1718
editors?: Array<MetadataEditor>,
1819
isCascadingPolicyApplicable?: boolean,
1920
isDropdownBusy?: boolean,
@@ -35,6 +36,7 @@ type Props = {
3536
const MetadataInstanceEditor = ({
3637
blurExceptionClassNames,
3738
canAdd,
39+
canUseAIFolderExtraction = false,
3840
isCascadingPolicyApplicable = false,
3941
isDropdownBusy,
4042
editors = [],
@@ -61,6 +63,7 @@ const MetadataInstanceEditor = ({
6163
) : (
6264
<ScrollWrapper>
6365
<Instances
66+
canUseAIFolderExtraction={canUseAIFolderExtraction}
6467
editors={editors}
6568
isCascadingPolicyApplicable={isCascadingPolicyApplicable}
6669
onModification={onModification}

src/features/metadata-instance-editor/__tests__/CascadePolicy.test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as React from 'react';
22

3+
import { screen, render, within } from '../../../test-utils/testing-library';
4+
35
import CascadePolicy from '../CascadePolicy';
46

57
describe('features/metadata-instance-editor/CascadePolicy', () => {
@@ -48,4 +50,46 @@ describe('features/metadata-instance-editor/CascadePolicy', () => {
4850
);
4951
expect(wrapper).toMatchSnapshot();
5052
});
53+
54+
test('should render AI folder extraction toggle when canEdit, canUseAIFolderExtraction, and shouldShowCascadeOptions are true', () => {
55+
render(<CascadePolicy canEdit canUseAIFolderExtraction shouldShowCascadeOptions />);
56+
expect(screen.getByText('Box AI Autofill')).toBeInTheDocument();
57+
});
58+
59+
test.each([
60+
[false, false, false],
61+
[true, false, false],
62+
[false, true, false],
63+
[true, true, false],
64+
])(
65+
'should not render AI folder extraction toggle when canEdit, canUseAIFolderExtraction, and shouldShowCascadeOptions are %s, %s, and %s',
66+
(canEdit, canUseAIFolderExtraction, shouldShowCascadeOptions) => {
67+
render(
68+
<CascadePolicy
69+
canEdit={canEdit}
70+
canUseAIFolderExtraction={canUseAIFolderExtraction}
71+
shouldShowCascadeOptions={shouldShowCascadeOptions}
72+
/>,
73+
);
74+
expect(screen.queryByText('Box AI Autofill')).not.toBeInTheDocument();
75+
},
76+
);
77+
78+
describe('AI Autofill Links', () => {
79+
test('should render AI and pricing links when AI features are enabled', () => {
80+
render(<CascadePolicy canEdit canUseAIFolderExtraction shouldShowCascadeOptions />);
81+
82+
// Find link within the AI autofill section since there are two links with the same text in the component
83+
const aiSection = screen.getByTestId('ai-folder-extraction');
84+
const aiLink = within(aiSection).getByText('Learn more');
85+
expect(aiLink).toBeInTheDocument();
86+
expect(aiLink.closest('a')).toHaveAttribute('href', 'https://www.box.com/ai');
87+
expect(aiLink.closest('a')).toHaveAttribute('target', '_blank');
88+
89+
const pricingLink = screen.getByText('pricing details');
90+
expect(pricingLink).toBeInTheDocument();
91+
expect(pricingLink.closest('a')).toHaveAttribute('href', 'https://www.box.com/pricing');
92+
expect(pricingLink.closest('a')).toHaveAttribute('target', '_blank');
93+
});
94+
});
5195
});

0 commit comments

Comments
 (0)