Skip to content

Commit bc3b631

Browse files
author
Daniel Duong
committed
fix: address comment
1 parent 1f17fec commit bc3b631

4 files changed

Lines changed: 57 additions & 33 deletions

File tree

packages/automl/frontend/src/app/api/s3.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
// Modules -------------------------------------------------------------------->
22

3+
import { APIOptions, handleRestFailures, isModArchResponse, restGET } from 'mod-arch-core';
34
import * as z from 'zod';
4-
import {
5-
APIOptions,
6-
handleRestFailures,
7-
isModArchResponse,
8-
restCREATE,
9-
restGET,
10-
} from 'mod-arch-core';
11-
import { BFF_API_VERSION, URL_PREFIX } from '~/app/utilities/const';
125
import type { S3ListObjectsResponse } from '~/app/types';
6+
import { BFF_API_VERSION, URL_PREFIX } from '~/app/utilities/const';
137

148
// Globals -------------------------------------------------------------------->
159

@@ -74,6 +68,7 @@ export type GetFilesOptions = {
7468
* @param params - namespace, secretName, key (required); bucket (optional, uses secret default if omitted)
7569
* @param file - The file to upload (sent as multipart form field "file")
7670
* @returns Promise that resolves when upload succeeds; throws on non-2xx response or malformed 2xx body
71+
* @throws Error with statusCode property for HTTP error responses (e.g., 409 for filename collision)
7772
*/
7873
export async function uploadFileToS3(
7974
hostPath: string,
@@ -92,16 +87,32 @@ export async function uploadFileToS3(
9287
const formData = new FormData();
9388
formData.append('file', file, file.name);
9489

90+
const searchParams = new URLSearchParams(queryParams).toString();
9591
const path = `${URL_PREFIX}/api/${BFF_API_VERSION}/s3/file`;
92+
const url = `${hostPath}${path}?${searchParams}`;
93+
94+
const response = await fetch(url, {
95+
method: 'POST',
96+
body: formData,
97+
});
98+
99+
const responseData = await response.json();
100+
101+
if (!response.ok) {
102+
// Extract error message from BFF error envelope
103+
const errorMessage =
104+
responseData?.error?.message || `Upload failed with status ${response.status}`;
105+
// Attach statusCode to error for UI to discriminate error types
106+
const error = Object.assign(new Error(errorMessage), { statusCode: response.status });
107+
throw error;
108+
}
96109

97-
const response = await handleRestFailures(restCREATE(hostPath, path, formData, queryParams));
98-
99-
if (!isS3UploadSuccessPayload(response)) {
110+
if (!isS3UploadSuccessPayload(responseData)) {
100111
throw new Error(
101112
'Invalid upload response: expected uploaded: true and a non-empty key from server',
102113
);
103114
}
104-
return response;
115+
return responseData;
105116
}
106117

107118
/**

packages/automl/frontend/src/app/components/configure/AutomlConfigure.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ import {
2929
MultipleFileUpload,
3030
MultipleFileUploadMain,
3131
NumberInput,
32+
Spinner,
3233
Split,
3334
SplitItem,
34-
Spinner,
3535
Stack,
3636
StackItem,
3737
ToggleGroup,
@@ -40,17 +40,17 @@ import {
4040
Truncate,
4141
type DropEvent,
4242
} from '@patternfly/react-core';
43-
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
4443
import { CubesIcon, EllipsisVIcon, TimesIcon, UploadIcon } from '@patternfly/react-icons';
44+
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
4545
import { useQueryClient } from '@tanstack/react-query';
4646
import { findKey } from 'es-toolkit';
4747
import React, { useCallback, useEffect, useRef, useState } from 'react';
4848
import { Controller, useFormContext, useWatch } from 'react-hook-form';
4949
import { Navigate, useParams } from 'react-router';
5050
import AutomlConnectionModal from '~/app/components/common/AutomlConnectionModal';
5151
import ConfigureFormGroup from '~/app/components/common/ConfigureFormGroup';
52-
import S3FileExplorer from '~/app/components/common/S3FileExplorer/S3FileExplorer.tsx';
5352
import type { File as S3ExplorerFile } from '~/app/components/common/FileExplorer/FileExplorer.tsx';
53+
import S3FileExplorer from '~/app/components/common/S3FileExplorer/S3FileExplorer.tsx';
5454
import SecretSelector, { SecretSelection } from '~/app/components/common/SecretSelector';
5555
import { useS3FileUploadMutation } from '~/app/hooks/mutations';
5656
import { useS3GetFileSchemaQuery } from '~/app/hooks/queries';
@@ -72,9 +72,9 @@ import {
7272
} from '~/app/utilities/const';
7373
import { automlExperimentsPathname } from '~/app/utilities/routes';
7474
import { getMissingRequiredKeys } from '~/app/utilities/secretValidation';
75+
import './AutomlConfigure.css';
7576
import ConfigureTabularForm from './ConfigureTabularForm';
7677
import ConfigureTimeseriesForm from './ConfigureTimeseriesForm';
77-
import './AutomlConfigure.css';
7878

7979
const PREDICTION_TYPES: {
8080
value: ConfigureSchema['task_type'];
@@ -343,7 +343,8 @@ function AutomlConfigure(): React.JSX.Element {
343343
} catch (err) {
344344
if (uploadRequestId === trainingDataUploadSeqRef.current) {
345345
const errorMessage = err instanceof Error ? err.message : String(err);
346-
const isConflict = errorMessage.toLowerCase().includes('unique filename');
346+
// Check for 409 Conflict status code (filename collision)
347+
const isConflict = err instanceof Error && 'statusCode' in err && err.statusCode === 409;
347348

348349
notification.error(
349350
'Failed to upload file',

packages/autorag/frontend/src/app/api/s3.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
// Modules -------------------------------------------------------------------->
22

3+
import { APIOptions, handleRestFailures, isModArchResponse, restGET } from 'mod-arch-core';
34
import * as z from 'zod';
4-
import {
5-
APIOptions,
6-
handleRestFailures,
7-
isModArchResponse,
8-
restCREATE,
9-
restGET,
10-
} from 'mod-arch-core';
11-
import { BFF_API_VERSION, URL_PREFIX } from '~/app/utilities/const';
125
import type { S3ListObjectsResponse } from '~/app/types';
6+
import { BFF_API_VERSION, URL_PREFIX } from '~/app/utilities/const';
137

148
// Globals -------------------------------------------------------------------->
159

@@ -74,6 +68,7 @@ export type GetFilesOptions = {
7468
* @param params - namespace, secretName, key (required); bucket (optional, uses secret default if omitted)
7569
* @param file - The file to upload (sent as multipart form field "file")
7670
* @returns Promise that resolves when upload succeeds; throws on non-2xx response or malformed 2xx body
71+
* @throws Error with statusCode property for HTTP error responses (e.g., 409 for filename collision)
7772
*/
7873
export async function uploadFileToS3(
7974
hostPath: string,
@@ -92,16 +87,32 @@ export async function uploadFileToS3(
9287
const formData = new FormData();
9388
formData.append('file', file, file.name);
9489

90+
const searchParams = new URLSearchParams(queryParams).toString();
9591
const path = `${URL_PREFIX}/api/${BFF_API_VERSION}/s3/file`;
92+
const url = `${hostPath}${path}?${searchParams}`;
93+
94+
const response = await fetch(url, {
95+
method: 'POST',
96+
body: formData,
97+
});
98+
99+
const responseData = await response.json();
100+
101+
if (!response.ok) {
102+
// Extract error message from BFF error envelope
103+
const errorMessage =
104+
responseData?.error?.message || `Upload failed with status ${response.status}`;
105+
// Attach statusCode to error for UI to discriminate error types
106+
const error = Object.assign(new Error(errorMessage), { statusCode: response.status });
107+
throw error;
108+
}
96109

97-
const response = await handleRestFailures(restCREATE(hostPath, path, formData, queryParams));
98-
99-
if (!isS3UploadSuccessPayload(response)) {
110+
if (!isS3UploadSuccessPayload(responseData)) {
100111
throw new Error(
101112
'Invalid upload response: expected uploaded: true and a non-empty key from server',
102113
);
103114
}
104-
return response;
115+
return responseData;
105116
}
106117

107118
/**

packages/autorag/frontend/src/app/components/configure/AutoragConfigure.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ import { Controller, useFormContext, useWatch, Watch } from 'react-hook-form';
6161
import { Navigate, useParams } from 'react-router';
6262
import AutoragConnectionModal from '~/app/components/common/AutoragConnectionModal';
6363
import ConfigureFormGroup from '~/app/components/common/ConfigureFormGroup';
64-
import S3FileExplorer from '~/app/components/common/S3FileExplorer/S3FileExplorer.tsx';
6564
import type { File as S3File } from '~/app/components/common/FileExplorer/FileExplorer.tsx';
65+
import S3FileExplorer from '~/app/components/common/S3FileExplorer/S3FileExplorer.tsx';
6666
import SecretSelector, { SecretSelection } from '~/app/components/common/SecretSelector';
6767
import { useS3FileUploadMutation } from '~/app/hooks/mutations';
6868
import { useLlamaStackModelsQuery } from '~/app/hooks/queries';
@@ -75,15 +75,15 @@ import {
7575
RAG_METRIC_CONTEXT_CORRECTNESS,
7676
RAG_METRIC_FAITHFULNESS,
7777
} from '~/app/schemas/configure.schema';
78-
import { OPTIMIZATION_METRIC_LABELS } from '~/app/utilities/const';
7978
import { SecretListItem } from '~/app/types';
79+
import { OPTIMIZATION_METRIC_LABELS } from '~/app/utilities/const';
8080
import { autoragExperimentsPathname } from '~/app/utilities/routes';
8181
import { getMissingRequiredKeys } from '~/app/utilities/secretValidation';
82+
import './AutoragConfigure.css';
8283
import AutoragEvaluationSelect from './AutoragEvaluationSelect';
8384
import AutoragExperimentSettings from './AutoragExperimentSettings';
8485
import AutoragVectorStoreSelector from './AutoragVectorStoreSelector';
8586
import EvaluationTemplateModal from './EvaluationTemplateModal';
86-
import './AutoragConfigure.css';
8787

8888
const AUTORAG_REQUIRED_KEYS: { [type: string]: string[] } = {
8989
s3: ['AWS_S3_BUCKET', 'AWS_DEFAULT_REGION'],
@@ -330,7 +330,8 @@ function AutoragConfigure(): React.JSX.Element {
330330
} catch (err) {
331331
if (uploadRequestId === inputDataUploadSeqRef.current) {
332332
const errorMessage = err instanceof Error ? err.message : String(err);
333-
const isConflict = errorMessage.toLowerCase().includes('unique filename');
333+
// Check for 409 Conflict status code (filename collision)
334+
const isConflict = err instanceof Error && 'statusCode' in err && err.statusCode === 409;
334335

335336
notification.error(
336337
'Failed to upload file',

0 commit comments

Comments
 (0)