diff --git a/src/components/task.vue b/src/components/task.vue index 49b516682..ea9d9185c 100644 --- a/src/components/task.vue +++ b/src/components/task.vue @@ -295,7 +295,7 @@ export default { } }, loadTask() { - const url = `/${this.taskId}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission`; + const url = `/${this.taskId}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination`; // For Vocabularies if (window.ProcessMaker && window.ProcessMaker.packages && window.ProcessMaker.packages.includes('package-vocabularies')) { window.ProcessMaker.VocabulariesSchemaUrl = `vocabularies/task_schema/${this.taskId}`; @@ -385,21 +385,17 @@ export default { * @param {string|null} parentRequestId - The parent request ID. */ closeTask(parentRequestId = null) { - const invoker = new TaskInvoker(); - - if (this.hasErrors) { - invoker.setCommand(new EmitErrorCommand(this)); - } - - if (this.shouldLoadNextTask()) { - invoker.setCommand(new LoadNextTaskCommand(this)); - } else if (this.task.allow_interstitial) { - invoker.setCommand(new ShowInterstitialCommand(this)); - } else if (!this.taskPreview) { - invoker.setCommand(new EmitClosedEventCommand(this)); - } + if (this.hasErrors) { + this.emitError(); + } - invoker.executeCommands(parentRequestId); + if (this.shouldLoadNextTask()) { + this.loadNextAssignedTask(parentRequestId); + } else if (this.task.allow_interstitial) { + this.showInterstitial(parentRequestId); + } else if (!this.taskPreview) { + this.emitClosedEvent(); + } }, /** @@ -407,7 +403,9 @@ export default { * @returns {boolean} - True if the next task should be loaded, otherwise false. */ shouldLoadNextTask() { - return this.task.process_request.status === 'COMPLETED' || this.loadingButton; + return ( + this.task.process_request.status === "COMPLETED" || this.loadingButton + ); }, /** * Shows the interstitial screen and loads the next assigned task. @@ -425,7 +423,7 @@ export default { * Emits an error event. */ emitError() { - this.$emit('error', this.requestId); + this.$emit('error', this.requestId); }, /** * Emits a closed event. @@ -438,12 +436,25 @@ export default { * @returns {string|null} - The destination URL. */ getDestinationUrl() { - // If the element destination is 'taskSource', use the document referrer - if (this.task?.elementDestination === 'taskSource') { - return document.referrer; + // If the element destination type is 'taskSource', use the document referrer + if (this.task?.elementDestination?.type === "taskSource") { + return document.referrer || null; + } + + // If element destination URL is available, return it + const elementDestinationUrl = this.task?.elementDestination?.value; + if (elementDestinationUrl) { + return elementDestinationUrl; + } + + // If no element destination URL, try to get it from sessionStorage + const sessionStorageUrl = sessionStorage.getItem("elementDestinationURL"); + if (sessionStorageUrl) { + return sessionStorageUrl; } - // If element destination is not set, try to get it from sessionStorage - return this.task.elementDestination || sessionStorage.getItem('elementDestinationURL') || null; + + // If none of the above conditions are met, return null + return null; }, loadNextAssignedTask(requestId = null) { if (!requestId) { @@ -535,7 +546,112 @@ export default { this.$emit('completed', requestId); } if (requestId !== this.requestId) { - this.$emit('completed', this.requestId, data?.endEventDestination); + this.processCompletedRedirect(data, this.userId, this.requestId); + } + }, + /** + * Makes an API call with retry logic. + * @param {Function} apiCall - The API call to be made. + * @param {number} retries - The number of retry attempts. + * @param {number} delay - The delay between retries in milliseconds. + * @returns {Promise} - The response from the API call. + */ + // eslint-disable-next-line consistent-return + async retryApiCall(apiCall, retries = 3, delay = 1000) { + for (let attempt = 0; attempt < retries; attempt++) { + try { + // eslint-disable-next-line no-await-in-loop + const response = await apiCall(); + return response; + } catch (error) { + if (attempt === retries - 1) { + throw error; + } + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => { + setTimeout(resolve, delay); + }); + } + } + }, + /** + * Gets the next request by posting to the specified endpoint. + * @param {string} processId - The process ID. + * @param {string} startEvent - The start event. + * @returns {Promise} - The response from the API call. + */ + getNextRequest(processId, startEvent) { + return window.ProcessMaker.apiClient.post( + `/process_events/${processId}?event=${startEvent}`, + {} + ); + }, + + /** + * Gets the tasks for the specified process request ID. + * @param {string} processRequestId - The process request ID. + * @returns {Promise} - The response from the API call. + */ + getTasks(processRequestId) { + return window.ProcessMaker.apiClient.get( + `tasks?process_request_id=${processRequestId}&page=1&per_page=1` + ); + }, + /** + * Parses a JSON string and returns the result. + * @param {string} jsonString - The JSON string to parse. + * @returns {object|null} - The parsed object or null if parsing fails. + */ + parseJsonSafely(jsonString) { + try { + return JSON.parse(jsonString); + } catch (error) { + console.error("Invalid JSON string:", error); + return null; + } + }, + /** + * Handles the process completion and redirects the user based on the task assignment. + * @param {object} data - The data object containing endEventDestination. + * @param {string} userId - The ID of the current user. + * @param {string} requestId - The ID of the current request. + */ + async processCompletedRedirect(data, userId, requestId) { + try { + // Verify if is not anotherProcess type + if (data.endEventDestination.type !== "anotherProcess") { + this.$emit( + "completed", + this.requestId, + data?.endEventDestination.value + ); + return; + } + // Parse endEventDestination from the provided data + const endEventDestination = this.parseJsonSafely( + data.endEventDestination.value + ); + // Get the next request using retry logic + const nextRequest = await this.retryApiCall(() => + this.getNextRequest( + endEventDestination.processId, + endEventDestination.startEvent + ) + ); + // Get the tasks for the next request using retry logic + const response = await this.retryApiCall(() => + this.getTasks(nextRequest.data.id) + ); + // Handle the first task from the response + const firstTask = response.data.data[0]; + if (firstTask && firstTask.user_id === userId) { + this.$emit("completed", requestId, `/tasks/${firstTask.id}/edit`); + } else { + this.$emit("completed", requestId); + } + } catch (error) { + console.error("Error processing completed redirect:", error); + this.$emit("completed", requestId); } }, getAllowedRequestId() { @@ -561,7 +677,6 @@ export default { this.processCompleted(data); } ); - this.addSocketListener( `updated-${this.requestId}`, `ProcessMaker.Models.ProcessRequest.${this.requestId}`, @@ -664,51 +779,4 @@ export default { this.unsubscribeSocketListeners(); }, }; -// Command classes -class Command { - constructor(receiver) { - this.receiver = receiver; - } - - execute() { - throw new Error('execute method must be implemented'); - } -} - -class LoadNextTaskCommand extends Command { - execute(parentRequestId) { - this.receiver.loadNextAssignedTask(parentRequestId); - } -} - -class ShowInterstitialCommand extends Command { - execute(parentRequestId) { - this.receiver.showInterstitial(parentRequestId); - } -} -class EmitErrorCommand extends Command { - execute() { - this.receiver.emitError(); - } -} -class EmitClosedEventCommand extends Command { - execute() { - this.receiver.emitClosedEvent(); - } -} - -class TaskInvoker { - constructor() { - this.commands = []; - } - - setCommand(command) { - this.commands.push(command); - } - - executeCommands(parentRequestId) { - this.commands.forEach(command => command.execute(parentRequestId)); - this.commands = []; // Clear the commands after execution - } -} diff --git a/tests/e2e/specs/MultiInstanceLoopContext.spec.js b/tests/e2e/specs/MultiInstanceLoopContext.spec.js index 8c4a9fdf6..e31a7030b 100644 --- a/tests/e2e/specs/MultiInstanceLoopContext.spec.js +++ b/tests/e2e/specs/MultiInstanceLoopContext.spec.js @@ -5,7 +5,7 @@ describe("FOUR-3375 FileUpload inside MultiInstance Task", () => { beforeEach(() => { cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { id: 1, advanceStatus: "open", diff --git a/tests/e2e/specs/Task.spec.js b/tests/e2e/specs/Task.spec.js index 9eabf940d..0fdcd3e82 100644 --- a/tests/e2e/specs/Task.spec.js +++ b/tests/e2e/specs/Task.spec.js @@ -24,7 +24,7 @@ describe("Task component", () => { it("Task inside a Request", () => { cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { id: 1, advanceStatus: "open", @@ -79,7 +79,7 @@ describe("Task component", () => { it("Completes the Task", () => { cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { id: 1, advanceStatus: "open", @@ -141,7 +141,7 @@ describe("Task component", () => { .then(function () { cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { id: 1, advanceStatus: "completed", @@ -158,7 +158,7 @@ describe("Task component", () => { it("Progresses to the interstitial screen", () => { cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { id: 1, advanceStatus: "open", @@ -312,7 +312,7 @@ describe("Task component", () => { }); cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { body: completedBodyRequest } ); cy.reload(); @@ -328,7 +328,7 @@ describe("Task component", () => { it("It updates the PM4ConfigOverrides", () => { cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { id: 1, advanceStatus: "open", @@ -394,7 +394,7 @@ describe("Task component", () => { it("Task with display next assigned task checked with another pending task in same request should redirect to the next task of same request", () => { cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { id: 1, advanceStatus: "open", @@ -424,7 +424,7 @@ describe("Task component", () => { }; getTask( - `http://localhost:5173/api/1.0/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission`, + `http://localhost:5173/api/1.0/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination`, responseDataTask1 ); @@ -450,7 +450,7 @@ describe("Task component", () => { }; getTask( - `http://localhost:5173/api/1.0/tasks/${responseDataTask2.taskId}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission`, + `http://localhost:5173/api/1.0/tasks/${responseDataTask2.taskId}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination`, responseDataTask2 ); @@ -470,7 +470,7 @@ describe("Task component", () => { it("Task with display next assigned task checked in subprocess and no pending task and status closed or open should redirect to parent requests", () => { cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { id: 1, advanceStatus: "open", @@ -501,7 +501,7 @@ describe("Task component", () => { }; getTask( - `http://localhost:5173/api/1.0/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission`, + `http://localhost:5173/api/1.0/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination`, responseDataTask1 ); @@ -520,7 +520,7 @@ describe("Task component", () => { }; getTask( - `http://localhost:5173/api/1.0/tasks/${responseDataTask2.taskId}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission`, + `http://localhost:5173/api/1.0/tasks/${responseDataTask2.taskId}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination`, responseDataTask2 ); @@ -541,7 +541,7 @@ describe("Task component", () => { it("Task with display next assigned task checked in different process request should redirect to the next task of parent request", () => { cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { id: 1, advanceStatus: "open", @@ -571,7 +571,7 @@ describe("Task component", () => { }; getTask( - `http://localhost:5173/api/1.0/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission`, + `http://localhost:5173/api/1.0/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination`, responseDataTask1 ); @@ -596,7 +596,7 @@ describe("Task component", () => { }; getTask( - `http://localhost:5173/api/1.0/tasks/${responseDataTask2.taskId}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission`, + `http://localhost:5173/api/1.0/tasks/${responseDataTask2.taskId}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination`, responseDataTask2 ); @@ -616,7 +616,7 @@ describe("Task component", () => { it("Task with display next assigned task unchecked should redirect to tasks list", () => { cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { id: 1, advanceStatus: "open", @@ -646,7 +646,7 @@ describe("Task component", () => { }; getTask( - `http://localhost:5173/api/1.0/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission`, + `http://localhost:5173/api/1.0/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination`, responseDataTask1 ); @@ -666,7 +666,7 @@ describe("Task component", () => { it("Process without pending task should redirect to request", () => { cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { id: 1, advanceStatus: "open", @@ -696,7 +696,7 @@ describe("Task component", () => { }; getTask( - `http://localhost:5173/api/1.0/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission`, + `http://localhost:5173/api/1.0/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination`, responseDataTask1 ); @@ -723,7 +723,7 @@ describe("Task component", () => { it("Subprocess without pending task should redirect to parent request", () => { cy.intercept( "GET", - "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission", + "http://localhost:5173/api/1.0/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", { id: 1, advanceStatus: "open", @@ -760,7 +760,7 @@ describe("Task component", () => { }; getTask( - `http://localhost:5173/api/1.0/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission`, + `http://localhost:5173/api/1.0/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination`, responseDataTask1 );