Skip to content

Commit f19d0e8

Browse files
authored
Address issues with accounting integration (#83)
* Resolve routing conflict for accounting service * Add other fixes
1 parent 5d57d2d commit f19d0e8

File tree

10 files changed

+70
-61
lines changed

10 files changed

+70
-61
lines changed

src/app/api/accounting/reservation/oneshot/[reservationId]/route.ts renamed to src/app/api/public-accounting/reservation/oneshot/[reservationId]/route.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { convertObjectKeystoCamelCase } from '@/util/object-keys-format';
22
import { auth } from '@/auth';
33
import { accountingBaseUrl } from '@/config';
44
import authFetch from '@/authFetch';
5-
import { assertApiResponse } from '@/util/utils';
5+
import { assertApiResponse, RemoteAPIErrorResponse } from '@/util/utils';
66

77
export const DELETE = async (
88
request: Request,
@@ -25,15 +25,10 @@ export const DELETE = async (
2525
headers: { 'Content-Type': 'application/json' },
2626
});
2727

28-
const resObj = assertApiResponse(res);
28+
const resObj = await assertApiResponse(res);
2929

3030
return Response.json(convertObjectKeystoCamelCase(resObj));
3131
} catch (error: unknown) {
32-
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
33-
34-
return new Response('Failed to cancel oneshot reservation', {
35-
status: 502,
36-
statusText: errorMessage,
37-
});
32+
return RemoteAPIErrorResponse(error);
3833
}
3934
};

src/app/api/accounting/reservation/oneshot/route.ts renamed to src/app/api/public-accounting/reservation/oneshot/route.ts

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import { OneshotReservation, ServiceType } from '@/types/accounting';
2-
import {
3-
convertObjectKeystoCamelCase,
4-
convertObjectKeysToSnakeCase,
5-
} from '@/util/object-keys-format';
2+
import { convertObjectKeystoCamelCase } from '@/util/object-keys-format';
63
import { auth } from '@/auth';
74
import { accountingBaseUrl } from '@/config';
85
import authFetch from '@/authFetch';
9-
import { assertApiResponse } from '@/util/utils';
6+
import { assertApiResponse, RemoteAPIErrorResponse } from '@/util/utils';
107
import { getVirtualLabProjectUsers } from '@/services/virtual-lab/projects';
118

129
export const POST = async (request: Request) => {
@@ -35,24 +32,19 @@ export const POST = async (request: Request) => {
3532
const res = await authFetch(`${accountingBaseUrl}/reservation/oneshot`, {
3633
method: 'POST',
3734
headers: { 'Content-Type': 'application/json' },
38-
body: JSON.stringify(
39-
convertObjectKeysToSnakeCase({
40-
...reservationRequest,
41-
userId: session.user.id,
42-
type: ServiceType.Oneshot,
43-
})
44-
),
35+
body: JSON.stringify({
36+
proj_id: projectId,
37+
user_id: session.user.id,
38+
type: ServiceType.Oneshot,
39+
subtype: reservationRequest.subtype,
40+
count: reservationRequest.count,
41+
}),
4542
});
4643

47-
const resObj = assertApiResponse(res);
44+
const resObj = await assertApiResponse(res);
4845

4946
return Response.json(convertObjectKeystoCamelCase(resObj));
50-
} catch (error: unknown) {
51-
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
52-
53-
return new Response('Failed to create oneshot reservation', {
54-
status: 502,
55-
statusText: errorMessage,
56-
});
47+
} catch (error) {
48+
return RemoteAPIErrorResponse(error);
5749
}
5850
};
Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
import { OneshotUsage } from '@/types/accounting';
2-
import {
3-
convertObjectKeystoCamelCase,
4-
convertObjectKeysToSnakeCase,
5-
} from '@/util/object-keys-format';
1+
import { OneshotUsage, ServiceType } from '@/types/accounting';
2+
import { convertObjectKeystoCamelCase } from '@/util/object-keys-format';
63
import { auth } from '@/auth';
74
import { accountingBaseUrl } from '@/config';
85
import authFetch from '@/authFetch';
9-
import { assertApiResponse } from '@/util/utils';
6+
import { assertApiResponse, RemoteAPIErrorResponse } from '@/util/utils';
107

118
export const POST = async (request: Request) => {
129
const usage = (await request.json()) as OneshotUsage;
@@ -23,18 +20,20 @@ export const POST = async (request: Request) => {
2320
const res = await authFetch(`${accountingBaseUrl}/usage/oneshot`, {
2421
method: 'POST',
2522
headers: { 'Content-Type': 'application/json' },
26-
body: JSON.stringify(convertObjectKeysToSnakeCase(usage)),
23+
body: JSON.stringify({
24+
type: ServiceType.Oneshot,
25+
subtype: usage.subtype,
26+
proj_id: usage.projectId,
27+
count: usage.count,
28+
job_id: usage.jobId,
29+
timestamp: usage.timestamp,
30+
}),
2731
});
2832

29-
const resObj = assertApiResponse(res);
33+
const resObj = await assertApiResponse(res);
3034

3135
return Response.json(convertObjectKeystoCamelCase(resObj));
3236
} catch (error: unknown) {
33-
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
34-
35-
return new Response('Failed to send oneshot usage', {
36-
status: 502,
37-
statusText: errorMessage,
38-
});
37+
return RemoteAPIErrorResponse(error);
3938
}
4039
};

src/app/app/virtual-lab/lab/[virtualLabId]/project/[projectId]/(secondary)/build/me-model/new/configure/page.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ import { queryAtom } from '@/state/explore-section/list-view-atoms';
2525
import { ExploreDataScope } from '@/types/explore-section/application';
2626
import { DataType } from '@/constants/explore-section/list-views';
2727

28+
const DEFAULT_ERROR_MSG = 'Somethng went wrong while creating the ME-model, please try again later';
29+
const LOW_FUNDS_ERROR_MSG =
30+
'The project does not have enough credits to create a model, please add more and try again';
31+
const LOW_FUNDS_ERROR_CODE = 'INSUFFICIENT_FUNDS';
32+
2833
type Params = {
2934
params: {
3035
virtualLabId: string;
@@ -149,8 +154,15 @@ export default function NewMEModelPage({ params: { projectId, virtualLabId } }:
149154
setMeModelCreating(false);
150155
router.push(redirectionUrl);
151156
})
152-
.catch(() => {
157+
.catch((err) => {
153158
setMeModelCreating(false);
159+
notification.error({
160+
duration: 10,
161+
message:
162+
err?.cause?.error_code === LOW_FUNDS_ERROR_CODE
163+
? LOW_FUNDS_ERROR_MSG
164+
: DEFAULT_ERROR_MSG,
165+
});
154166
});
155167
};
156168

src/components/LandingPage/layout/FooterPanel/NewsLetterSubscription/NewsLetterSubscription.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
}
2828

2929
.accepted {
30-
max-width: max(300px, 32%);
30+
max-width: max(300px, 32%);
3131
}
3232

3333
.accepted > svg {

src/components/build-section/virtual-lab/synaptome/molecules/SynaptomeConfigurationForm.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ import { validateFormula } from '@/api/bluenaas/validateSynapseGenerationFormula
4848
import { OneshotSession } from '@/services/accounting';
4949
import { ServiceSubtype } from '@/types/accounting';
5050

51+
const LOW_FUNDS_ERROR_MSG =
52+
'The project does not have enough credits to create a model, please add more and try again';
53+
const LOW_FUNDS_ERROR_CODE = 'INSUFFICIENT_FUNDS';
54+
5155
const label = (text: string) => (
5256
<span className="text-base font-semibold text-primary-8">{text}</span>
5357
);
@@ -284,16 +288,6 @@ export default function SynaptomeConfigurationForm({ org, project, resource }: P
284288

285289
refreshSynaptomeModels();
286290

287-
if (!resp.ok) {
288-
return notifyError(
289-
CREATE_SYNAPTOME_FAIL,
290-
undefined,
291-
'topRight',
292-
undefined,
293-
'synaptome-config'
294-
);
295-
}
296-
297291
form.resetFields();
298292
setSimulationScope(SimulationType.Synaptome);
299293
selectedRowsAtom.setShouldRemove(() => true); // set function to remove all
@@ -305,7 +299,11 @@ export default function SynaptomeConfigurationForm({ org, project, resource }: P
305299

306300
navigate(generateSynaptomeUrl(newSynaptomeModel));
307301
} catch (error) {
308-
notifyError(CREATE_SYNAPTOME_FAIL, 7, 'topRight', undefined, 'synaptome-config');
302+
const errorMessage =
303+
(error as any)?.cause?.error_code === LOW_FUNDS_ERROR_CODE
304+
? LOW_FUNDS_ERROR_MSG
305+
: CREATE_SYNAPTOME_FAIL;
306+
notifyError(errorMessage, 7, 'topRight', undefined, 'synaptome-config');
309307
setLoading(false);
310308
}
311309
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export const INTERACTIVE_PATH = `/app/explore/interactive/`;
1+
export const INTERACTIVE_PATH = `/explore/interactive/`;
22
export const BASE_EXPERIMENTAL_EXPLORE_PATH = `${INTERACTIVE_PATH}experimental/`;
33
export const BASE_MODEL_EXPLORE_PATH = `${INTERACTIVE_PATH}model/`;

src/services/accounting/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ type OneshotUsageReport = Omit<OneshotUsage, 'type'>;
99
async function makeOneshotReservation(
1010
reservation: OneShotReservationRequest
1111
): Promise<OneshotReservationResponse> {
12-
const res = await authFetch('/api/accounting/reservation/oneshot', {
12+
const res = await authFetch('/api/public-accounting/reservation/oneshot', {
1313
method: 'POST',
1414
headers: { 'Content-Type': 'application/json' },
1515
body: JSON.stringify(reservation),
@@ -19,15 +19,15 @@ async function makeOneshotReservation(
1919
}
2020

2121
async function cancelOneshotReservation(jobId: string) {
22-
const res = await authFetch(`/api/accounting/reservation/oneshot/${jobId}`, {
22+
const res = await authFetch(`/api/public-accounting/reservation/oneshot/${jobId}`, {
2323
method: 'DELETE',
2424
});
2525

2626
return assertApiResponse(res);
2727
}
2828

2929
async function reportOneshotUsage(oneshotUsageReport: OneshotUsageReport) {
30-
const res = await authFetch('/api/accounting/usage/oneshot', {
30+
const res = await authFetch('/api/public-accounting/usage/oneshot', {
3131
method: 'POST',
3232
headers: { 'Content-Type': 'application/json' },
3333
body: JSON.stringify(oneshotUsageReport),
@@ -65,7 +65,7 @@ export class OneshotSession {
6565
reportOneshotUsage({
6666
...this.reservationRequest,
6767
jobId,
68-
timestamp: new Date().toISOString(),
68+
timestamp: Math.floor(Date.now() / 1000),
6969
});
7070
}
7171

src/types/accounting/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,11 @@ export type OneshotReservationResponse = VlmResponse<{
9494
}>;
9595

9696
export type OneshotUsage = {
97+
virtualLabId: string;
9798
projectId: string;
9899
type: ServiceType;
99100
subtype: ServiceSubtype;
100101
count: number;
101102
jobId: string;
102-
timestamp: string;
103+
timestamp: number;
103104
};

src/util/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,13 +266,25 @@ export async function assertApiResponse(res: Response) {
266266
throw new Error(message || 'An error occurred while processing your request...', {
267267
cause: {
268268
...data,
269+
status: res.status,
269270
},
270271
});
271272
}
272273

273274
return data;
274275
}
275276

277+
export function RemoteAPIErrorResponse(error: any, status: number = 502, defaultMessage?: string) {
278+
const message = error.message ?? defaultMessage;
279+
280+
const resData = {
281+
...(error instanceof Error && typeof error.cause === 'object' ? error.cause : null),
282+
message,
283+
};
284+
285+
return Response.json(resData, { status });
286+
}
287+
276288
export function assertErrorMessage(e: any) {
277289
if (e instanceof Error) return e.message;
278290
return 'Something went wrong...';

0 commit comments

Comments
 (0)