Skip to content

Commit 77b0a42

Browse files
feat: add waitForTaskCompletion
1 parent 0048290 commit 77b0a42

File tree

5 files changed

+196
-9
lines changed

5 files changed

+196
-9
lines changed

packages/sdk/src/lib/dataProtectorCore/IExecDataProtectorCore.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
RevokedAccess,
1818
TransferParams,
1919
TransferResponse,
20+
WaitForTaskCompletionResponse,
21+
WaitForTaskCompletionParams,
2022
} from '../types/index.js';
2123
import { getGrantedAccess } from './getGrantedAccess.js';
2224
import { getProtectedData } from './getProtectedData.js';
@@ -27,6 +29,7 @@ import { protectData } from './protectData.js';
2729
import { revokeAllAccess } from './revokeAllAccess.js';
2830
import { revokeOneAccess } from './revokeOneAccess.js';
2931
import { transferOwnership } from './transferOwnership.js';
32+
import { waitForTaskCompletion } from './waitForTaskCompletion.js';
3033

3134
class IExecDataProtectorCore extends IExecDataProtectorModule {
3235
async protectData(
@@ -99,6 +102,17 @@ class IExecDataProtectorCore extends IExecDataProtectorModule {
99102
return getGrantedAccess({ ...args, iexec: this.iexec });
100103
}
101104

105+
async waitForTaskCompletion(
106+
args: WaitForTaskCompletionParams
107+
): Promise<WaitForTaskCompletionResponse> {
108+
await this.init();
109+
await isValidProvider(this.iexec);
110+
return waitForTaskCompletion({
111+
...args,
112+
iexec: this.iexec,
113+
});
114+
}
115+
102116
async getResultFromCompletedTask(
103117
args: GetResultFromCompletedTaskParams
104118
): Promise<GetResultFromCompletedTaskResponse> {

packages/sdk/src/lib/dataProtectorCore/processProtectedData.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { IExecConsumer, VoucherInfo } from '../types/internalTypes.js';
4040
import { getResultFromCompletedTask } from './getResultFromCompletedTask.js';
4141
import { getWhitelistContract } from './smartContract/getWhitelistContract.js';
4242
import { isAddressInWhitelist } from './smartContract/whitelistContract.read.js';
43+
import { waitForTaskCompletion } from './waitForTaskCompletion.js';
4344

4445
export type ProcessProtectedData = typeof processProtectedData;
4546

@@ -399,15 +400,11 @@ export const processProtectedData = async <
399400
taskId: taskId,
400401
},
401402
});
402-
const taskObservable = await iexec.task.obsTask(taskId, { dealid: dealid });
403-
await new Promise((resolve, reject) => {
404-
taskObservable.subscribe({
405-
next: () => {},
406-
error: (e) => {
407-
reject(e);
408-
},
409-
complete: () => resolve(undefined),
410-
});
403+
404+
await waitForTaskCompletion({
405+
iexec,
406+
dealid,
407+
taskId,
411408
});
412409

413410
vOnStatusUpdate({
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { WorkflowError } from '../../utils/errors.js';
2+
import {
3+
taskIdSchema,
4+
throwIfMissing,
5+
validateOnStatusUpdateCallback,
6+
} from '../../utils/validators.js';
7+
import {
8+
OnStatusUpdateFn,
9+
WaitForTaskCompletionParams,
10+
WaitForTaskCompletionResponse,
11+
WaitForTaskCompletionStatuses,
12+
} from '../types/index.js';
13+
import { IExecConsumer } from '../types/internalTypes.js';
14+
15+
export const waitForTaskCompletion = async ({
16+
iexec = throwIfMissing(),
17+
dealid,
18+
taskId,
19+
onStatusUpdate = () => {},
20+
}: IExecConsumer &
21+
WaitForTaskCompletionParams): Promise<WaitForTaskCompletionResponse> => {
22+
const vTaskId = taskIdSchema()
23+
.required()
24+
.label('taskId')
25+
.validateSync(taskId);
26+
const vDealId = taskIdSchema()
27+
.required()
28+
.label('dealId')
29+
.validateSync(dealid);
30+
const vOnStatusUpdate =
31+
validateOnStatusUpdateCallback<
32+
OnStatusUpdateFn<WaitForTaskCompletionStatuses>
33+
>(onStatusUpdate);
34+
35+
try {
36+
const taskObservable = await iexec.task.obsTask(vTaskId, {
37+
dealid: vDealId,
38+
});
39+
let status: 'COMPLETED' | 'FAILED' | 'TIMEOUT';
40+
let success: boolean;
41+
await new Promise((resolve, reject) => {
42+
taskObservable.subscribe({
43+
next: (data) => {
44+
const isDone =
45+
data?.task?.statusName === 'COMPLETED' ||
46+
data?.task?.statusName === 'FAILED' ||
47+
data?.task?.statusName === 'TIMEOUT';
48+
if (isDone) {
49+
status = data?.task?.statusName as
50+
| 'COMPLETED'
51+
| 'FAILED'
52+
| 'TIMEOUT';
53+
success = data?.task?.statusName === 'COMPLETED';
54+
}
55+
vOnStatusUpdate({
56+
title: 'TASK_UPDATED',
57+
isDone,
58+
payload: {
59+
taskId: vTaskId,
60+
status: data?.task?.statusName,
61+
},
62+
});
63+
},
64+
error: (e) => {
65+
reject(e);
66+
},
67+
complete: () => resolve(undefined),
68+
});
69+
});
70+
return { status, success };
71+
} catch (error) {
72+
console.error('Error in waitForTaskCompletion:', error);
73+
throw new WorkflowError({
74+
message: 'Failed to wait for task completion',
75+
errorCause: error,
76+
});
77+
}
78+
};

packages/sdk/src/lib/types/coreTypes.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,21 @@ export type GrantedAccessResponse = {
208208
grantedAccess: GrantedAccess[];
209209
};
210210

211+
// ---------------------waitForTaskCompletion Types------------------------------------
212+
213+
export type WaitForTaskCompletionStatuses = 'TASK_UPDATED';
214+
215+
export type WaitForTaskCompletionParams = {
216+
taskId: string;
217+
dealid: string;
218+
onStatusUpdate?: OnStatusUpdateFn<WaitForTaskCompletionStatuses>;
219+
};
220+
221+
export type WaitForTaskCompletionResponse = {
222+
status: 'COMPLETED' | 'FAILED' | 'TIMEOUT';
223+
success: boolean;
224+
};
225+
211226
// ---------------------GetResultFromCompletedTask Types------------------------------------
212227

213228
export type GetResultFromCompletedTaskStatuses =
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { beforeAll, describe, expect, it, jest } from '@jest/globals';
2+
import { Wallet } from 'ethers';
3+
import { IExecDataProtectorCore } from '../../../src/index.js';
4+
import { getTestConfig } from '../../test-utils.js';
5+
6+
describe('dataProtectorCore.waitForTaskCompletion()', () => {
7+
let dataProtectorCore: IExecDataProtectorCore;
8+
9+
beforeAll(async () => {
10+
dataProtectorCore = new IExecDataProtectorCore(
11+
...getTestConfig(Wallet.createRandom().privateKey)
12+
);
13+
});
14+
15+
it('should return when the task is completed', async () => {
16+
// https://explorer.iex.ec/bellecour/task/0xb4655f62bdb841a3b54363b113c4204bf4fee76ab9029f33dc1218ab495970d7
17+
const onStatusUpdate = jest.fn();
18+
const COMPLETED_TASKID =
19+
'0xb4655f62bdb841a3b54363b113c4204bf4fee76ab9029f33dc1218ab495970d7';
20+
const COMPLETED_DEALID =
21+
'0xb5091be0385c80545cdd12e7c678b96dbb6338cf699324f8f2aa94d3f33f6eda';
22+
const res = await dataProtectorCore.waitForTaskCompletion({
23+
dealid: COMPLETED_DEALID,
24+
taskId: COMPLETED_TASKID,
25+
onStatusUpdate,
26+
});
27+
expect(res).toEqual({ status: 'COMPLETED', success: true });
28+
expect(onStatusUpdate).toHaveBeenLastCalledWith({
29+
title: 'TASK_UPDATED',
30+
isDone: true,
31+
payload: {
32+
taskId: COMPLETED_TASKID,
33+
status: 'COMPLETED',
34+
},
35+
});
36+
});
37+
38+
it('should return when the task is failed', async () => {
39+
// https://explorer.iex.ec/bellecour/task/0x000b16d5517e44ca70744ec156e8374ae525c9ab902169fe01d909370e5778e0
40+
const FAILED_TASKID =
41+
'0x000b16d5517e44ca70744ec156e8374ae525c9ab902169fe01d909370e5778e0';
42+
const FAILED_DEALID =
43+
'0xd613b7c6c4a022efe129fd93ce547eba71fc1055e0b42d20b11ad1f3505ad0a5';
44+
const onStatusUpdate = jest.fn();
45+
const res = await dataProtectorCore.waitForTaskCompletion({
46+
dealid: FAILED_DEALID,
47+
taskId: FAILED_TASKID,
48+
onStatusUpdate,
49+
});
50+
expect(res).toEqual({ status: 'FAILED', success: false });
51+
expect(onStatusUpdate).toHaveBeenLastCalledWith({
52+
title: 'TASK_UPDATED',
53+
isDone: true,
54+
payload: {
55+
taskId: FAILED_TASKID,
56+
status: 'FAILED',
57+
},
58+
});
59+
});
60+
61+
it('should return when the task is in timeout', async () => {
62+
// https://explorer.iex.ec/bellecour/task/0x012b3d2f21ea3c8c0cc2a40ce06df028df84d1b53b7dae98d5352e79427b93a6
63+
const TIMEOUT_TASKID =
64+
'0x012b3d2f21ea3c8c0cc2a40ce06df028df84d1b53b7dae98d5352e79427b93a6';
65+
const TIMEOUT_DEALID =
66+
'0xab15a51de7a3829fca1d3666b81b53e9e9ced0aa71bf20e7ebee1be1bdb3ee33';
67+
const onStatusUpdate = jest.fn();
68+
const res = await dataProtectorCore.waitForTaskCompletion({
69+
dealid: TIMEOUT_DEALID,
70+
taskId: TIMEOUT_TASKID,
71+
onStatusUpdate,
72+
});
73+
expect(res).toEqual({ status: 'TIMEOUT', success: false });
74+
expect(onStatusUpdate).toHaveBeenLastCalledWith({
75+
title: 'TASK_UPDATED',
76+
isDone: true,
77+
payload: {
78+
taskId: TIMEOUT_TASKID,
79+
status: 'TIMEOUT',
80+
},
81+
});
82+
});
83+
});

0 commit comments

Comments
 (0)