box-ui-elements 23.4.0-beta.11 → 23.4.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/explorer.css +1 -1
- package/dist/explorer.js +1 -1
- package/dist/picker.js +1 -1
- package/dist/preview.css +1 -1
- package/dist/preview.js +1 -1
- package/dist/sidebar.css +1 -1
- package/dist/sidebar.js +1 -1
- package/dist/uploader.js +1 -1
- package/es/api/schemas/AiAgentReference.js +2 -0
- package/es/api/schemas/AiAgentReference.js.flow +15 -0
- package/es/api/schemas/AiAgentReference.js.map +1 -0
- package/es/api/schemas/AiExtractStructured.js +1 -0
- package/es/api/schemas/AiExtractStructured.js.flow +7 -1
- package/es/api/schemas/AiExtractStructured.js.map +1 -1
- package/es/api/uploads/FolderUploadNode.js +41 -6
- package/es/api/uploads/FolderUploadNode.js.flow +45 -5
- package/es/api/uploads/FolderUploadNode.js.map +1 -1
- package/es/elements/content-sidebar/hooks/useSidebarMetadataFetcher.js +19 -12
- package/es/elements/content-sidebar/hooks/useSidebarMetadataFetcher.js.map +1 -1
- package/es/features/metadata-instance-fields/MetadataField.js +12 -1
- package/es/features/metadata-instance-fields/MetadataField.js.flow +14 -0
- package/es/features/metadata-instance-fields/MetadataField.js.map +1 -1
- package/es/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.d.ts +1 -1
- package/es/utils/sleep.js +3 -0
- package/es/utils/sleep.js.flow +2 -0
- package/es/utils/sleep.js.map +1 -0
- package/package.json +5 -5
- package/src/api/schemas/AiAgentReference.js +15 -0
- package/src/api/schemas/AiExtractStructured.js +7 -1
- package/src/api/uploads/FolderUploadNode.js +45 -5
- package/src/api/uploads/__tests__/FolderUploadNode.test.js +49 -0
- package/src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx +58 -0
- package/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts +11 -7
- package/src/features/metadata-instance-fields/MetadataField.js +14 -0
- package/src/features/metadata-instance-fields/__tests__/MetadataField.test.js +6 -0
- package/src/features/metadata-instance-fields/__tests__/__snapshots__/MetadataField.test.js.snap +7 -0
- package/src/utils/sleep.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "box-ui-elements",
|
|
3
|
-
"version": "23.4.0-beta.
|
|
3
|
+
"version": "23.4.0-beta.13",
|
|
4
4
|
"description": "Box UI Elements",
|
|
5
5
|
"author": "Box (https://www.box.com/)",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
"@babel/template": "^7.24.7",
|
|
125
125
|
"@babel/types": "^7.24.7",
|
|
126
126
|
"@box/blueprint-web": "^12.7.1",
|
|
127
|
-
"@box/blueprint-web-assets": "^4.
|
|
127
|
+
"@box/blueprint-web-assets": "^4.53.0",
|
|
128
128
|
"@box/box-ai-agent-selector": "^0.41.10",
|
|
129
129
|
"@box/box-ai-content-answers": "^0.124.1",
|
|
130
130
|
"@box/cldr-data": "^34.2.0",
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
"@box/frontend": "^11.0.1",
|
|
133
133
|
"@box/item-icon": "^0.9.83",
|
|
134
134
|
"@box/languages": "^1.0.0",
|
|
135
|
-
"@box/metadata-editor": "^0.
|
|
135
|
+
"@box/metadata-editor": "^0.112.0",
|
|
136
136
|
"@box/react-virtualized": "9.22.3-rc-box.9",
|
|
137
137
|
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
|
|
138
138
|
"@chromatic-com/storybook": "^1.6.1",
|
|
@@ -296,13 +296,13 @@
|
|
|
296
296
|
},
|
|
297
297
|
"peerDependencies": {
|
|
298
298
|
"@box/blueprint-web": "^12.7.1",
|
|
299
|
-
"@box/blueprint-web-assets": "^4.
|
|
299
|
+
"@box/blueprint-web-assets": "^4.53.0",
|
|
300
300
|
"@box/box-ai-agent-selector": "^0.41.10",
|
|
301
301
|
"@box/box-ai-content-answers": "^0.124.1",
|
|
302
302
|
"@box/cldr-data": ">=34.2.0",
|
|
303
303
|
"@box/combobox-with-api": "^0.34.9",
|
|
304
304
|
"@box/item-icon": "^0.9.83",
|
|
305
|
-
"@box/metadata-editor": "^0.
|
|
305
|
+
"@box/metadata-editor": "^0.112.0",
|
|
306
306
|
"@box/react-virtualized": "9.22.3-rc-box.9",
|
|
307
307
|
"@hapi/address": "^2.1.4",
|
|
308
308
|
"axios": "^0.30.0",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flow
|
|
3
|
+
* @author Box
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type AiAgentTypeField = 'ai_agent_id';
|
|
7
|
+
|
|
8
|
+
export interface AiAgentReference {
|
|
9
|
+
/**
|
|
10
|
+
* AI Agent Reference to pass custom AI Agent ID to requests.
|
|
11
|
+
* See https://developer.box.com/reference/resources/ai-agent-reference/
|
|
12
|
+
*/
|
|
13
|
+
+type: AiAgentTypeField;
|
|
14
|
+
+id: string;
|
|
15
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { AiAgentExtractStructured } from './AiAgentExtractStructured';
|
|
7
|
+
import { AiAgentReference } from './AiAgentReference';
|
|
7
8
|
import { AiItemBase } from './AiItemBase';
|
|
8
9
|
|
|
9
10
|
export type AiExtractStructuredMetadataTemplateTypeField = 'metadata_template';
|
|
@@ -80,5 +81,10 @@ export interface AiExtractStructured {
|
|
|
80
81
|
* The JSON blob that contains overrides for the agent config.
|
|
81
82
|
*/
|
|
82
83
|
+agent_config?: string;
|
|
83
|
-
|
|
84
|
+
/**
|
|
85
|
+
* * AI agent definition to use for extraction.
|
|
86
|
+
* – `AiAgentExtractStructured`: customise Basic-Text / Long-Text agents
|
|
87
|
+
* – `AiAgentReference` : reference a custom AI-Agent by ID
|
|
88
|
+
*/
|
|
89
|
+
+ai_agent?: AiAgentExtractStructured | AiAgentReference;
|
|
84
90
|
}
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
STATUS_ERROR,
|
|
12
12
|
ERROR_CODE_UPLOAD_CHILD_FOLDER_FAILED,
|
|
13
13
|
ERROR_CODE_ITEM_NAME_IN_USE,
|
|
14
|
+
DEFAULT_RETRY_DELAY_MS,
|
|
15
|
+
MS_IN_S,
|
|
14
16
|
} from '../../constants';
|
|
15
17
|
import type {
|
|
16
18
|
UploadFileWithAPIOptions,
|
|
@@ -18,6 +20,10 @@ import type {
|
|
|
18
20
|
FolderUploadItem,
|
|
19
21
|
DirectoryReader,
|
|
20
22
|
} from '../../common/types/upload';
|
|
23
|
+
import sleep from '../../utils/sleep';
|
|
24
|
+
|
|
25
|
+
const CHILD_FOLDER_UPLOAD_CONCURRENCY = 3;
|
|
26
|
+
const MAX_RETRIES = 3;
|
|
21
27
|
|
|
22
28
|
class FolderUploadNode {
|
|
23
29
|
addFolderToUploadQueue: Function;
|
|
@@ -93,11 +99,30 @@ class FolderUploadNode {
|
|
|
93
99
|
* @returns {Promise}
|
|
94
100
|
*/
|
|
95
101
|
uploadChildFolders = async (errorCallback: Function) => {
|
|
102
|
+
// Gets FolderUploadNode values from this.folders key value pairs object
|
|
96
103
|
// $FlowFixMe
|
|
97
104
|
const folders: Array<FolderUploadNode> = Object.values(this.folders);
|
|
98
|
-
const promises = folders.map(folder => folder.upload(this.folderId, errorCallback));
|
|
99
105
|
|
|
100
|
-
|
|
106
|
+
// Worker function: picks the next folder from the array and uploads until no more folders are available
|
|
107
|
+
const worker = async () => {
|
|
108
|
+
while (folders.length > 0) {
|
|
109
|
+
const folder = folders.pop();
|
|
110
|
+
if (folder) {
|
|
111
|
+
// Await is needed to help ensure rate limit is respected
|
|
112
|
+
// eslint-disable-next-line no-await-in-loop
|
|
113
|
+
await folder.upload(this.folderId, errorCallback);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Spawns up to CHILD_FOLDER_UPLOAD_CONCURRENCY workers that upload folders in parallel until folders array is empty
|
|
119
|
+
const workers = [];
|
|
120
|
+
for (let i = 0; i < CHILD_FOLDER_UPLOAD_CONCURRENCY && i < folders.length; i += 1) {
|
|
121
|
+
workers.push(worker());
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Waits for all workers to finish
|
|
125
|
+
await Promise.all(workers);
|
|
101
126
|
};
|
|
102
127
|
|
|
103
128
|
/**
|
|
@@ -108,7 +133,7 @@ class FolderUploadNode {
|
|
|
108
133
|
* @param {boolean} isRoot
|
|
109
134
|
* @returns {Promise}
|
|
110
135
|
*/
|
|
111
|
-
createAndUploadFolder = async (errorCallback: Function, isRoot: boolean) => {
|
|
136
|
+
createAndUploadFolder = async (errorCallback: Function, isRoot: boolean, retryCount: number = 0) => {
|
|
112
137
|
await this.buildCurrentFolderFromEntry();
|
|
113
138
|
|
|
114
139
|
let errorEncountered = false;
|
|
@@ -117,9 +142,23 @@ class FolderUploadNode {
|
|
|
117
142
|
const data = await this.createFolder();
|
|
118
143
|
this.folderId = data.id;
|
|
119
144
|
} catch (error) {
|
|
120
|
-
// @TODO: Handle 429
|
|
121
145
|
if (error.code === ERROR_CODE_ITEM_NAME_IN_USE) {
|
|
122
146
|
this.folderId = error.context_info.conflicts[0].id;
|
|
147
|
+
} else if (error.status === 429 && retryCount < MAX_RETRIES) {
|
|
148
|
+
// Set a default exponential backoff delay with a random jitter(0–999 ms) to avoid all requests being sent at once
|
|
149
|
+
// This will be overridden if the Retry-After header is present in the response
|
|
150
|
+
let retryAfterMs = DEFAULT_RETRY_DELAY_MS * 2 ** retryCount + Math.floor(Math.random() * 1000);
|
|
151
|
+
if (error.headers) {
|
|
152
|
+
const retryAfterHeaderSec = parseInt(
|
|
153
|
+
error.headers['retry-after'] || error.headers.get('Retry-After'),
|
|
154
|
+
10,
|
|
155
|
+
);
|
|
156
|
+
if (!Number.isNaN(retryAfterHeaderSec)) {
|
|
157
|
+
retryAfterMs = retryAfterHeaderSec * MS_IN_S;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
await sleep(retryAfterMs);
|
|
161
|
+
return this.createAndUploadFolder(errorCallback, isRoot, retryCount + 1);
|
|
123
162
|
} else if (isRoot) {
|
|
124
163
|
errorCallback(error);
|
|
125
164
|
} else {
|
|
@@ -135,7 +174,7 @@ class FolderUploadNode {
|
|
|
135
174
|
|
|
136
175
|
// The root folder has already been added to the upload queue in ContentUploader
|
|
137
176
|
if (isRoot) {
|
|
138
|
-
return;
|
|
177
|
+
return undefined;
|
|
139
178
|
}
|
|
140
179
|
|
|
141
180
|
const folderObject: FolderUploadItem = {
|
|
@@ -153,6 +192,7 @@ class FolderUploadNode {
|
|
|
153
192
|
}
|
|
154
193
|
|
|
155
194
|
this.addFolderToUploadQueue(folderObject);
|
|
195
|
+
return undefined;
|
|
156
196
|
};
|
|
157
197
|
|
|
158
198
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import noop from 'lodash/noop';
|
|
2
2
|
import FolderUploadNode from '../FolderUploadNode';
|
|
3
3
|
import FolderAPI from '../../Folder';
|
|
4
|
+
import sleep from '../../../utils/sleep';
|
|
4
5
|
import {
|
|
5
6
|
ERROR_CODE_ITEM_NAME_IN_USE,
|
|
6
7
|
STATUS_COMPLETE,
|
|
@@ -12,6 +13,7 @@ jest.mock('../../../utils/uploads', () => ({
|
|
|
12
13
|
...jest.requireActual('../../../utils/uploads'),
|
|
13
14
|
getFileFromEntry: jest.fn(entry => entry),
|
|
14
15
|
}));
|
|
16
|
+
jest.mock('../../../utils/sleep', () => jest.fn(() => Promise.resolve()));
|
|
15
17
|
|
|
16
18
|
let folderUploadNodeInstance;
|
|
17
19
|
let folderCreateMock;
|
|
@@ -30,6 +32,10 @@ describe('api/uploads/FolderUploadNode', () => {
|
|
|
30
32
|
}));
|
|
31
33
|
});
|
|
32
34
|
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
jest.restoreAllMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
33
39
|
describe('upload()', () => {
|
|
34
40
|
test('should call createAndUploadFolder(), addFilesToUploadQueue() and uploadChildFolders()', async () => {
|
|
35
41
|
const errorCallback = () => 'errorCallback';
|
|
@@ -182,6 +188,49 @@ describe('api/uploads/FolderUploadNode', () => {
|
|
|
182
188
|
|
|
183
189
|
expect(folderUploadNodeInstance.addFolderToUploadQueue).not.toHaveBeenCalled();
|
|
184
190
|
});
|
|
191
|
+
|
|
192
|
+
test('should retry on 429 with default delay', async () => {
|
|
193
|
+
const error = { status: 429 };
|
|
194
|
+
const success = { id: '123' };
|
|
195
|
+
const createFolder = jest.fn().mockRejectedValueOnce(error).mockResolvedValueOnce(success);
|
|
196
|
+
folderUploadNodeInstance.createFolder = createFolder;
|
|
197
|
+
folderUploadNodeInstance.addFolderToUploadQueue = jest.fn();
|
|
198
|
+
|
|
199
|
+
await folderUploadNodeInstance.createAndUploadFolder(jest.fn(), false, 0);
|
|
200
|
+
|
|
201
|
+
expect(createFolder).toHaveBeenCalledTimes(2);
|
|
202
|
+
expect(folderUploadNodeInstance.folderId).toBe('123');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test.each`
|
|
206
|
+
headersMock | description
|
|
207
|
+
${{ 'retry-after': '2' }} | ${'lower case plain object'}
|
|
208
|
+
${{ get: key => (key === 'Retry-After' ? '2' : undefined) }} | ${'capitalized map object'}
|
|
209
|
+
`('should handle $description headers', async ({ headersMock }) => {
|
|
210
|
+
const error = { status: 429, headers: headersMock };
|
|
211
|
+
const success = { id: '123' };
|
|
212
|
+
const createFolder = jest.fn().mockRejectedValueOnce(error).mockResolvedValueOnce(success);
|
|
213
|
+
folderUploadNodeInstance.createFolder = createFolder;
|
|
214
|
+
folderUploadNodeInstance.addFolderToUploadQueue = jest.fn();
|
|
215
|
+
|
|
216
|
+
await folderUploadNodeInstance.createAndUploadFolder(jest.fn(), false, 0);
|
|
217
|
+
|
|
218
|
+
expect(sleep).toHaveBeenCalledWith(2000);
|
|
219
|
+
expect(createFolder).toHaveBeenCalledTimes(2);
|
|
220
|
+
expect(folderUploadNodeInstance.folderId).toBe('123');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('should stop retrying after max retries', async () => {
|
|
224
|
+
const error = { status: 429 };
|
|
225
|
+
const createFolder = jest.fn().mockRejectedValue(error);
|
|
226
|
+
folderUploadNodeInstance.createFolder = createFolder;
|
|
227
|
+
folderUploadNodeInstance.addFolderToUploadQueue = jest.fn();
|
|
228
|
+
|
|
229
|
+
await folderUploadNodeInstance.createAndUploadFolder(jest.fn(), false, 3);
|
|
230
|
+
|
|
231
|
+
expect(createFolder).toHaveBeenCalledTimes(1);
|
|
232
|
+
expect(folderUploadNodeInstance.folderId).toBeUndefined();
|
|
233
|
+
});
|
|
185
234
|
});
|
|
186
235
|
|
|
187
236
|
describe('getFormattedFiles()', () => {
|
|
@@ -36,6 +36,7 @@ const mockPreconditionFailedError = {
|
|
|
36
36
|
const mockFile = {
|
|
37
37
|
id: '123',
|
|
38
38
|
permissions: { [FIELD_PERMISSIONS_CAN_UPLOAD]: true },
|
|
39
|
+
type: 'file',
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
const mockTemplates = [
|
|
@@ -423,5 +424,62 @@ describe('useSidebarMetadataFetcher', () => {
|
|
|
423
424
|
}),
|
|
424
425
|
);
|
|
425
426
|
});
|
|
427
|
+
|
|
428
|
+
test('should call extractStructured with custom AI agent ID', async () => {
|
|
429
|
+
const { result } = setupHook();
|
|
430
|
+
const agentId = 'custom-agent-123';
|
|
431
|
+
|
|
432
|
+
await result.current.extractSuggestions('templateKey', 'global', agentId);
|
|
433
|
+
|
|
434
|
+
expect(mockAPI.extractStructured).toHaveBeenCalledWith({
|
|
435
|
+
items: [{ id: mockFile.id, type: mockFile.type }],
|
|
436
|
+
metadata_template: { template_key: 'templateKey', scope: 'global', type: 'metadata_template' },
|
|
437
|
+
ai_agent: { type: 'ai_agent_id', id: agentId },
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test('should not call extractStructured with custom AI agent ID', async () => {
|
|
442
|
+
const { result } = setupHook();
|
|
443
|
+
|
|
444
|
+
await result.current.extractSuggestions('templateKey', 'global');
|
|
445
|
+
|
|
446
|
+
// Assert that ai_agent is NOT present
|
|
447
|
+
expect(mockAPI.extractStructured).toHaveBeenCalledWith(
|
|
448
|
+
expect.not.objectContaining({
|
|
449
|
+
ai_agent: expect.anything(),
|
|
450
|
+
}),
|
|
451
|
+
);
|
|
452
|
+
// Also verify what IS present
|
|
453
|
+
expect(mockAPI.extractStructured).toHaveBeenCalledWith({
|
|
454
|
+
items: [{ id: mockFile.id, type: mockFile.type }],
|
|
455
|
+
metadata_template: { template_key: 'templateKey', scope: 'global', type: 'metadata_template' },
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test('should handle undefined agentIDs', async () => {
|
|
460
|
+
const { result } = setupHook();
|
|
461
|
+
|
|
462
|
+
await result.current.extractSuggestions('templateKey', 'global', undefined);
|
|
463
|
+
|
|
464
|
+
// Assert that ai_agent is NOT present
|
|
465
|
+
expect(mockAPI.extractStructured).toHaveBeenCalledWith(
|
|
466
|
+
expect.not.objectContaining({
|
|
467
|
+
ai_agent: expect.anything(),
|
|
468
|
+
}),
|
|
469
|
+
);
|
|
470
|
+
// Also verify what IS present
|
|
471
|
+
expect(mockAPI.extractStructured).toHaveBeenCalledWith({
|
|
472
|
+
items: [{ id: mockFile.id, type: mockFile.type }],
|
|
473
|
+
metadata_template: { template_key: 'templateKey', scope: 'global', type: 'metadata_template' },
|
|
474
|
+
});
|
|
475
|
+
mockAPI.extractStructured.mockClear();
|
|
476
|
+
|
|
477
|
+
await result.current.extractSuggestions('templateKey', 'global', '');
|
|
478
|
+
expect(mockAPI.extractStructured).toHaveBeenCalledWith(
|
|
479
|
+
expect.not.objectContaining({
|
|
480
|
+
ai_agent: expect.anything(),
|
|
481
|
+
}),
|
|
482
|
+
);
|
|
483
|
+
});
|
|
426
484
|
});
|
|
427
485
|
});
|
|
@@ -29,6 +29,7 @@ import messages from '../../common/messages';
|
|
|
29
29
|
|
|
30
30
|
import { type BoxItem } from '../../../common/types/core';
|
|
31
31
|
import { type ErrorContextProps, type ExternalProps, type SuccessContextProps } from '../MetadataSidebarRedesign';
|
|
32
|
+
import { type AiExtractStructured } from '../../../api/schemas/AiExtractStructured';
|
|
32
33
|
|
|
33
34
|
export enum STATUS {
|
|
34
35
|
IDLE = 'idle',
|
|
@@ -45,7 +46,7 @@ interface DataFetcher {
|
|
|
45
46
|
| ERROR_CODE_METADATA_PRECONDITION_FAILED
|
|
46
47
|
| ERROR_CODE_UNKNOWN
|
|
47
48
|
| null;
|
|
48
|
-
extractSuggestions: (templateKey: string, scope: string) => Promise<MetadataTemplateField[]>;
|
|
49
|
+
extractSuggestions: (templateKey: string, scope: string, agentId?: string) => Promise<MetadataTemplateField[]>;
|
|
49
50
|
file: BoxItem | null;
|
|
50
51
|
handleCreateMetadataInstance: (
|
|
51
52
|
templateInstance: MetadataTemplateInstance,
|
|
@@ -214,16 +215,19 @@ function useSidebarMetadataFetcher(
|
|
|
214
215
|
|
|
215
216
|
const [, setError] = React.useState();
|
|
216
217
|
const extractSuggestions = React.useCallback(
|
|
217
|
-
async (templateKey: string, scope: string): Promise<MetadataTemplateField[]> => {
|
|
218
|
+
async (templateKey: string, scope: string, agentId?: string): Promise<MetadataTemplateField[]> => {
|
|
218
219
|
const aiAPI = api.getIntelligenceAPI();
|
|
219
220
|
setExtractErrorCode(null);
|
|
220
|
-
|
|
221
221
|
let answer = null;
|
|
222
|
+
const customAiAgent = agentId ? { ai_agent: { type: 'ai_agent_id', id: agentId } } : {};
|
|
223
|
+
const requestBody: AiExtractStructured = {
|
|
224
|
+
items: [{ id: file.id, type: file.type }],
|
|
225
|
+
metadata_template: { template_key: templateKey, scope, type: 'metadata_template' },
|
|
226
|
+
...customAiAgent,
|
|
227
|
+
};
|
|
228
|
+
|
|
222
229
|
try {
|
|
223
|
-
answer = (await aiAPI.extractStructured(
|
|
224
|
-
items: [{ id: file.id, type: file.type }],
|
|
225
|
-
metadata_template: { template_key: templateKey, scope, type: 'metadata_template' },
|
|
226
|
-
})) as Record<string, MetadataFieldValue>;
|
|
230
|
+
answer = (await aiAPI.extractStructured(requestBody)) as Record<string, MetadataFieldValue>;
|
|
227
231
|
} catch (error) {
|
|
228
232
|
// Axios makes the status code nested under the response object
|
|
229
233
|
if (error.response?.status === 408) {
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
FIELD_TYPE_STRING,
|
|
22
22
|
FIELD_TYPE_DATE,
|
|
23
23
|
FIELD_TYPE_MULTISELECT,
|
|
24
|
+
FIELD_TYPE_TAXONOMY,
|
|
24
25
|
} from './constants';
|
|
25
26
|
|
|
26
27
|
type Props = {
|
|
@@ -160,6 +161,19 @@ const MetadataField = ({
|
|
|
160
161
|
/>
|
|
161
162
|
);
|
|
162
163
|
|
|
164
|
+
// The taxonomy field is a valid field type which,
|
|
165
|
+
// although not yet supported here, should not trigger an error message.
|
|
166
|
+
// For this reason, we are currently setting it to read-only.
|
|
167
|
+
case FIELD_TYPE_TAXONOMY:
|
|
168
|
+
return (
|
|
169
|
+
<ReadOnlyMetadataField
|
|
170
|
+
dataValue={dataValue}
|
|
171
|
+
description={description}
|
|
172
|
+
displayName={displayName}
|
|
173
|
+
type={type}
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
|
|
163
177
|
default:
|
|
164
178
|
return (
|
|
165
179
|
<InlineError title={type}>
|
|
@@ -33,6 +33,12 @@ describe('features/metadata-instance-editor/fields/MetadataField', () => {
|
|
|
33
33
|
);
|
|
34
34
|
expect(wrapper).toMatchSnapshot();
|
|
35
35
|
});
|
|
36
|
+
test('should correctly render a taxonomy field - for the time being, in read-only mode', () => {
|
|
37
|
+
const wrapper = shallow(
|
|
38
|
+
<MetadataField canEdit dataValue="value" onChange={onChange} onRemove={onRemove} type="taxonomy" />,
|
|
39
|
+
);
|
|
40
|
+
expect(wrapper).toMatchSnapshot();
|
|
41
|
+
});
|
|
36
42
|
test('should correctly render a float field', () => {
|
|
37
43
|
const wrapper = shallow(
|
|
38
44
|
<MetadataField canEdit dataValue="value" onChange={onChange} onRemove={onRemove} type="float" />,
|
package/src/features/metadata-instance-fields/__tests__/__snapshots__/MetadataField.test.js.snap
CHANGED
|
@@ -37,6 +37,13 @@ exports[`features/metadata-instance-editor/fields/MetadataField should correctly
|
|
|
37
37
|
/>
|
|
38
38
|
`;
|
|
39
39
|
|
|
40
|
+
exports[`features/metadata-instance-editor/fields/MetadataField should correctly render a taxonomy field - for the time being, in read-only mode 1`] = `
|
|
41
|
+
<ReadOnlyMetadataField
|
|
42
|
+
dataValue="value"
|
|
43
|
+
type="taxonomy"
|
|
44
|
+
/>
|
|
45
|
+
`;
|
|
46
|
+
|
|
40
47
|
exports[`features/metadata-instance-editor/fields/MetadataField should correctly render a text field 1`] = `
|
|
41
48
|
<TextMetadataField
|
|
42
49
|
dataValue="value"
|