Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions i18n/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,14 @@ boxui.metadataInstanceEditor.customErrorDuplicateKey = A field with that key alr
boxui.metadataInstanceEditor.customErrorInternalKey = Keys cannot begin with a $.
# Error enforcing required key for custom metadata
boxui.metadataInstanceEditor.customErrorRequired = A key is required.
# Label for the button that navigates the user to manage the custom Box AI extract agent
boxui.metadataInstanceEditor.customExtractAgentManageButton = Manage agent
# Body of the notice shown when a metadata instance is managed by a custom Box AI extract agent
boxui.metadataInstanceEditor.customExtractAgentNoticeDescription = This metadata instance can't be edited here. To change which fields are extracted or update cascade policy settings, manage the agent.
# Aria label for the info icon on the custom extract agent notice
boxui.metadataInstanceEditor.customExtractAgentNoticeIconAriaLabel = Extract agent managed information
# Bold title of the notice shown when a metadata instance is managed by a custom Box AI extract agent
boxui.metadataInstanceEditor.customExtractAgentNoticeTitle = Managed by a Box AI extract agent
# Label for the key field for custom metadata
boxui.metadataInstanceEditor.customKey = Key
# Placeholder for the key field for custom metadata
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// @flow
import * as React from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { ActionableInlineNotice } from '@box/blueprint-web';
// $FlowFixMe - blueprint-web-assets icons not typed for Flow
import { Lock } from '@box/blueprint-web-assets/icons/Line';

import TemplatedInstance from './TemplatedInstance';
import CustomInstance from './CustomInstance';
import messages from './messages';
import { TEMPLATE_CUSTOM_PROPERTIES } from './constants';
import { getCustomExtractAgentId } from './metadataUtil';
import type { MetadataFields, MetadataTemplate } from '../../common/types/metadata';
Comment on lines +8 to +13
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Follow the import order convention

import './CustomExtractAgentInstanceBody.scss';

type Props = {
// Raw agent configuration value from the cascade policy (e.g. `extract_agent_<id>`).
// The navigable numeric id is resolved from this here.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"resolved from this here" seems a bit ambiguous. Maybe change into something like "The navigable numeric id is extracted from this value via getCustomExtractAgentId"?

agentConfiguration?: string,
data: MetadataFields,
isEditing: boolean,
onManageExtractAgent?: (agentId: string) => void,
template: MetadataTemplate,
};

/**
* Presentational interior for a metadata instance managed by a custom Box AI
* extract agent. When collapsed it shows read-only field values; when the user
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When collapsed it shows read-only field values

Is there a test to verify this is true? Correct me if my understanding of this component is incorrect: the InstanceCard parent controls collapsed/expanded state via a Collapsible — when collapsed, the entire body (including this component) is hidden and nothing renders. This component only deals with editing vs. not-editing via the isEditing prop.

* enters edit mode it replaces the form with an informational notice and a button
* to manage the agent (instead of allowing inline edits).
*
* The "manage agent" button is only shown when a navigable numeric agent id can be
* resolved from the configuration; otherwise the notice is shown without an action.
*/
const CustomExtractAgentInstanceBody = ({
agentConfiguration,
data,
isEditing,
onManageExtractAgent,
template,
}: Props) => {
const { formatMessage } = useIntl();
const isProperties = template.templateKey === TEMPLATE_CUSTOM_PROPERTIES;
const customExtractAgentId = getCustomExtractAgentId(agentConfiguration);

if (isEditing) {
return (
<div className="metadata-instance-editor-custom-extract-agent">
<ActionableInlineNotice
icon={Lock}
iconAriaLabel={formatMessage(messages.customExtractAgentNoticeIconAriaLabel)}
text={formatMessage(messages.customExtractAgentNoticeDescription)}
title={formatMessage(messages.customExtractAgentNoticeTitle)}
>
{!!customExtractAgentId && onManageExtractAgent && (
<ActionableInlineNotice.PrimaryAction
onClick={() => onManageExtractAgent(customExtractAgentId)}
>
{formatMessage(messages.customExtractAgentManageButton)}
</ActionableInlineNotice.PrimaryAction>
)}
</ActionableInlineNotice>
</div>
);
}

return (
<div className="metadata-instance-editor-instance">
<div className="metadata-cascade-notice">
<FormattedMessage {...messages.metadataCascadePolicyEnabledInfo} />
</div>
{isProperties ? (
<CustomInstance canEdit={false} data={data} />
) : (
<TemplatedInstance canEdit={false} data={data} errors={{}} template={template} />
)}
</div>
);
};

export default CustomExtractAgentInstanceBody;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.metadata-instance-editor-custom-extract-agent {
padding: 8px;
}
145 changes: 145 additions & 0 deletions src/features/metadata-instance-editor/EditableInstanceBody.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// @flow
import * as React from 'react';
import noop from 'lodash/noop';

import type { AgentType } from '@box/box-ai-agent-selector';
import Form from '../../components/form-elements/form/Form';
import LoadingIndicatorWrapper from '../../components/loading-indicator/LoadingIndicatorWrapper';

import CascadePolicy from './CascadePolicy';
import TemplatedInstance from './TemplatedInstance';
import CustomInstance from './CustomInstance';
import MetadataInstanceConfirmDialog from './MetadataInstanceConfirmDialog';
import Footer from './Footer';
import type {
MetadataCascadePolicy,
MetadataFields,
MetadataTemplate,
MetadataFieldValue,
} from '../../common/types/metadata';

type Props = {
canUseAIFolderExtraction: boolean,
cascadePolicy: MetadataCascadePolicy,
confirmationMessage: React.Node,
data: MetadataFields,
errors: { [string]: React.Node },
isAIFolderExtractionEnabled: boolean,
isBusy: boolean,
isCascadingEnabled: boolean,
isCascadingOverwritten: boolean,
isCascadingPolicyApplicable?: boolean,
isDirty: boolean,
isEditing: boolean,
isExistingCascadePolicy: boolean,
isProperties: boolean,
onAIAgentSelect: (agent: AgentType | null) => void,
onAIFolderExtractionToggle: (value: boolean) => void,
onCancel: () => void,
onCascadeModeChange: (value: boolean) => void,
onCascadeToggle: (value: boolean) => void,
onConfirmCancel: () => void,
onConfirmRemove: () => void,
onFieldChange: (key: string, value: MetadataFieldValue, type: string) => void,
onFieldRemove: (key: string) => void,
onRemove: () => void,
onSave: () => void,
shouldConfirmRemove: boolean,
shouldShowCascadeOptions: boolean,
template: MetadataTemplate,
};

/**
* Presentational interior for an editable metadata instance: the confirm-remove
* dialog, the form with cascade policy + fields, and the save/remove footer.
* All state and handlers are owned by the parent Instance and supplied via props.
*/
const EditableInstanceBody = ({
canUseAIFolderExtraction,
cascadePolicy = {},
confirmationMessage,
data,
errors,
isAIFolderExtractionEnabled,
isBusy,
isCascadingEnabled,
isCascadingOverwritten,
isCascadingPolicyApplicable,
isDirty,
isEditing,
isExistingCascadePolicy,
isProperties,
onAIAgentSelect,
onAIFolderExtractionToggle,
onCancel,
onCascadeModeChange,
onCascadeToggle,
onConfirmCancel,
onConfirmRemove,
onFieldChange,
onFieldRemove,
onRemove,
onSave,
shouldConfirmRemove,
shouldShowCascadeOptions,
template,
}: Props) => {
if (shouldConfirmRemove) {
return (
<LoadingIndicatorWrapper isLoading={isBusy}>
<MetadataInstanceConfirmDialog
confirmationMessage={confirmationMessage}
onCancel={onConfirmCancel}
onConfirm={onRemove}
/>
</LoadingIndicatorWrapper>
);
}

return (
<LoadingIndicatorWrapper isLoading={isBusy}>
<Form onValidSubmit={isDirty ? onSave : noop}>
<div className="metadata-instance-editor-instance">
{isCascadingPolicyApplicable && (
<CascadePolicy
cascadePolicyConfiguration={cascadePolicy?.cascadePolicyConfiguration}
canEdit={isEditing && !!cascadePolicy.canEdit}
canUseAIFolderExtraction={canUseAIFolderExtraction}
isAIFolderExtractionEnabled={isAIFolderExtractionEnabled}
isCascadingEnabled={isCascadingEnabled}
isCascadingOverwritten={isCascadingOverwritten}
isCustomMetadata={isProperties}
isExistingCascadePolicy={isExistingCascadePolicy}
onAIAgentSelect={onAIAgentSelect}
onAIFolderExtractionToggle={onAIFolderExtractionToggle}
onCascadeModeChange={onCascadeModeChange}
onCascadeToggle={onCascadeToggle}
shouldShowCascadeOptions={shouldShowCascadeOptions}
/>
)}
{isProperties ? (
<CustomInstance
canEdit={isEditing}
data={data}
onFieldChange={onFieldChange}
onFieldRemove={onFieldRemove}
/>
) : (
<TemplatedInstance
canEdit={isEditing}
data={data}
errors={errors}
isDisabled={isAIFolderExtractionEnabled}
onFieldChange={onFieldChange}
onFieldRemove={onFieldRemove}
template={template}
/>
)}
</div>
{isEditing && <Footer onCancel={onCancel} onRemove={onConfirmRemove} showSave={isDirty} />}
</Form>
</LoadingIndicatorWrapper>
);
};

export default EditableInstanceBody;
Loading
Loading