Skip to content

Commit

Permalink
vdk-jupyter: introduce Task Runner, a polling mechanism that runs tas…
Browse files Browse the repository at this point in the history
…ks in the background and tracks their status (#2869)

What: Introduce Task Runner, a polling mechanism that runs tasks in the
background and tracks their status.
Implements
[#2806](#2806) and
[#2807](#2807).

Why: Address the problem described in
[#2789](#2789).

Testing Done: CI/CD is passing. Introduced relevant tests

Signed-off-by: Yoan Salambashev <[email protected]>
  • Loading branch information
yonitoo authored Nov 16, 2023
1 parent b739832 commit 23fd3cf
Show file tree
Hide file tree
Showing 12 changed files with 530 additions and 124 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
"@jupyterlab/ui-components": "3.6.3",
"@lumino/widgets": "1.33.0",
"@types/react": "17.0.53",
"react-icons": "^4.11.0",
"react-icons": "4.11.0",
"typescript": "4.1.3",
"word-wrap": "1.2.4",
"yjs": "^13.5.17"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,26 +70,64 @@ describe('jobRunRequest()', () => {
jest.clearAllMocks();
});

it('should call requestAPI with correct arguments and return successful result', async () => {
const expectedMessage = '0';
const expectedResponse = { message: '0', isSuccessful: true };
(requestAPI as jest.Mock).mockResolvedValue(expectedResponse);
it('should call requestAPI to start a task and then poll for its completion, returning a successful result', async () => {
const mockData = {
[VdkOption.PATH]: '/test/job/path'
};
jobData.set(VdkOption.PATH, mockData[VdkOption.PATH]);

const taskId = 'RUN-6266cd99-908c-480b-9a3e-8a30564736a4';
const taskInitiationResponse = {
error: '',
message: `Task ${taskId} started`
};
const taskCompletionResponse = {
task_id: taskId,
status: 'completed',
message: 'Operation completed successfully',
error: null
};

(requestAPI as jest.Mock)
.mockResolvedValueOnce(taskInitiationResponse)
.mockResolvedValue(taskCompletionResponse);

const result = await jobRunRequest();

expect(requestAPI).toHaveBeenCalledTimes(1);
expect(result).toEqual({ message: expectedMessage, isSuccessful: true });
expect(requestAPI).toHaveBeenCalledWith('run', {
body: JSON.stringify(getJobDataJsonObject()),
method: 'POST'
});
expect(requestAPI).toHaveBeenCalledWith(`taskStatus?taskId=${taskId}`, {
method: 'GET'
});
expect(result).toEqual({
message: taskCompletionResponse.message,
isSuccessful: true
});
});

it('should call requestAPI with correct arguments and return unsuccessful result', async () => {
const expectedError = new Error('1');
(requestAPI as jest.Mock).mockResolvedValue(expectedError);
const mockData = {
[VdkOption.PATH]: '/test/job/path'
};
jobData.set(VdkOption.PATH, mockData[VdkOption.PATH]);

const taskId = 'RUN-6266cd99-908c-480b-9a3e-8a30564736a4';
const taskInitiationResponse = {
error: '1',
message: `Task ${taskId} started`
};
(requestAPI as jest.Mock).mockResolvedValueOnce(taskInitiationResponse);

const result = await jobRunRequest();

expect(requestAPI).toHaveBeenCalledTimes(1);
expect(requestAPI).toHaveBeenCalledWith('run', {
body: JSON.stringify(getJobDataJsonObject()),
method: 'POST'
});
expect(result).toEqual({
message: '1',
message: taskInitiationResponse.message,
isSuccessful: false
});
});
Expand All @@ -101,32 +139,79 @@ describe('jobRequest()', () => {
});

it('should call requestAPI with the correct arguments', async () => {
const endPoint = 'your-endpoint-url';
const expectedRequestBody = JSON.stringify(getJobDataJsonObject());
const expectedRequestMethod = 'POST';
const mockData = {
[VdkOption.NAME]: 'Test Job',
[VdkOption.TEAM]: 'Test Team'
};

jobData.set(VdkOption.NAME, mockData[VdkOption.NAME]);
jobData.set(VdkOption.TEAM, mockData[VdkOption.TEAM]);

const endpoint = 'CREATE';
const taskId = endpoint + '-6266cd99-908c-480b-9a3e-8a30564736a4';
const taskInitiationResponse = {
error: '',
message: `Task ${taskId} started`
};
const taskCompletionResponse = {
task_id: taskId,
status: 'completed',
message: 'Task completed successfully',
error: null
};

(requestAPI as jest.Mock)
.mockResolvedValueOnce(taskInitiationResponse)
.mockResolvedValue(taskCompletionResponse);

await jobRequest(endPoint);
const result = await jobRequest(endpoint);

expect(requestAPI).toHaveBeenCalledWith(endPoint, {
body: expectedRequestBody,
method: expectedRequestMethod
// Verify the call for initiating the task
expect(requestAPI).toHaveBeenCalledWith(endpoint, {
body: JSON.stringify(getJobDataJsonObject()),
method: 'POST'
});

// Verify the polling for task status
expect(requestAPI).toHaveBeenCalledWith(`taskStatus?taskId=${taskId}`, {
method: 'GET'
});

// Verify the final result
expect(result).toEqual({
message: taskCompletionResponse.message,
isSuccessful: true
});
});

it('should show a success message if requestAPI returns a serverVdkOperationResult without errors', async () => {
const endPoint = 'your-endpoint-url';
it('should show an error message if requestAPI returns a serverVdkOperationResult with error', async () => {
const mockData = {
[VdkOption.NAME]: 'Test Job',
[VdkOption.TEAM]: 'Test Team'
};

jobData.set(VdkOption.NAME, mockData[VdkOption.NAME]);
jobData.set(VdkOption.TEAM, mockData[VdkOption.TEAM]);

const endpoint = 'DEPLOY';
const serverVdkOperationResultMock = {
error: '',
message: 'Operation completed successfully'
error: '1',
message: `${endpoint} task failed`
};
(requestAPI as jest.Mock).mockResolvedValue(serverVdkOperationResultMock);
(requestAPI as jest.Mock).mockResolvedValueOnce(
serverVdkOperationResultMock
);

await jobRequest(endPoint);
const result = await jobRequest(endpoint);

expect(requestAPI).toHaveBeenCalledWith(endPoint, {
expect(requestAPI).toHaveBeenCalledWith(endpoint, {
body: JSON.stringify(getJobDataJsonObject()),
method: 'POST'
});
expect(result).toEqual({
message: serverVdkOperationResultMock.message,
isSuccessful: false
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,13 @@ export async function showConvertJobToNotebookDialog(
});
if (confirmation.button.accept) {
statusButton?.show('Convert', jobData.get(VdkOption.PATH)!);
let { message, status } = await jobConvertToNotebookRequest();
if (status) {
const transformjobResult = JSON.parse(message);
let { message, isSuccessful } = await jobConvertToNotebookRequest();
// if the job request is successful, it is not expected to return string but an object with code_structure and
// removed_files fields which will help us to create a notebook and populate it with the content
if (isSuccessful && typeof message !== 'string') {
notebookContent = initializeNotebookContent(
transformjobResult['code_structure'],
transformjobResult['removed_files']
message.code_structure,
message.removed_files
);
await createTranformedNotebook(commands, fileBrowser, notebookTracker);
await showDialog({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export const findFailingCellInNotebookCells = async (
};

/**
* Seperate handling for notebook errors - option for the user to navigate to the failing cell when error is produced
* Separate handling for notebook errors - option for the user to navigate to the failing cell when error is produced
*/
export const handleErrorsProducedByNotebookCell = async (
message: VdkErrorMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ export function getJobDataJsonObject() {
export async function checkIfVdkOptionDataIsDefined(
option: VdkOption
): Promise<boolean> {
if (jobData.get(option)) return true;
else {
if (jobData.get(option)) {
return true;
} else {
await showErrorMessage(
'Encountered an error while trying to execute operation. Error:',
'The ' + option + ' should be defined!',
Expand Down
Loading

0 comments on commit 23fd3cf

Please sign in to comment.