diff --git a/src/components/task.vue b/src/components/task.vue index 4061ba38..189a37ef 100644 --- a/src/components/task.vue +++ b/src/components/task.vue @@ -526,40 +526,64 @@ export default { // If the task has not an origin source it should re redirected top the tasks List as default. return document.referrer || '/tasks'; }, - loadNextAssignedTask(requestId = null) { + getActiveTasksUrl(requestId, includeUserFilter = true) { + const timestamp = !window.Cypress ? `&t=${Date.now()}` : ""; + const userFilter = includeUserFilter ? `user_id=${this.userId}&` : ""; + + return `?${userFilter}status=ACTIVE&process_request_id=${requestId}&include_sub_tasks=1${timestamp}`; + }, + isWaitingOnInterstitial() { + return Boolean(this.task?.allow_interstitial && _.get(this.screen, '_interstitial', false)); + }, + async redirectToRequestIfAnotherActiveTaskExists(requestId) { + if (!this.isWaitingOnInterstitial()) { + return false; + } + + const response = await this.$dataProvider.getTasks(this.getActiveTasksUrl(requestId, false)); + const activeTasks = _.get(response, 'data.data', []).filter((task) => task.id !== this.taskId); + + if (activeTasks.length > 0) { + this.redirectToRequest(requestId); + return true; + } + + return false; + }, + async loadNextAssignedTask(requestId = null) { if (!requestId) { requestId = this.requestId; } if (!this.userId) { return; } - const timestamp = !window.Cypress ? `&t=${Date.now()}` : ""; - const url = `?user_id=${this.userId}&status=ACTIVE&process_request_id=${requestId}&include_sub_tasks=1${timestamp}`; - return this.$dataProvider - .getTasks(url).then((response) => { - this.$emit("load-data-task", response); - if (response.data.data.length > 0) { - let task = response.data.data[0]; - if (task.process_request_id !== this.requestId) { - // Next task is in a subprocess, do a hard redirect - if (this.redirecting === task.process_request_id) { - return; - } - this.unsubscribeSocketListeners(); - this.redirecting = task.process_request_id; - this.$emit('redirect', task.id, true); - return; - } else { - this.emitIfTaskCompleted(requestId); - } - this.taskId = task.id; - this.nodeId = task.element_id; - this.loadTask(); - } else if (this.parentRequest && ['COMPLETED', 'CLOSED'].includes(this.task.process_request.status)) { - this.$emit('completed', this.getAllowedRequestId()); + + const response = await this.$dataProvider.getTasks(this.getActiveTasksUrl(requestId)); + + this.$emit("load-data-task", response); + if (response.data.data.length > 0) { + let task = response.data.data[0]; + if (task.process_request_id !== this.requestId) { + // Next task is in a subprocess, do a hard redirect + if (this.redirecting === task.process_request_id) { + return; } - this.disabled = false; - }); + this.unsubscribeSocketListeners(); + this.redirecting = task.process_request_id; + this.$emit('redirect', task.id, true); + return; + } else { + this.emitIfTaskCompleted(requestId); + } + this.taskId = task.id; + this.nodeId = task.element_id; + this.loadTask(); + } else if (await this.redirectToRequestIfAnotherActiveTaskExists(requestId)) { + return; + } else if (this.parentRequest && ['COMPLETED', 'CLOSED'].includes(this.task.process_request.status)) { + this.$emit('completed', this.getAllowedRequestId()); + } + this.disabled = false; }, emitIfTaskCompleted(requestId) { // Only emit completed after getting the subprocess tasks and there are no tasks and process is completed diff --git a/tests/e2e/specs/Task.spec.js b/tests/e2e/specs/Task.spec.js index 2e1aad18..d6c86557 100644 --- a/tests/e2e/specs/Task.spec.js +++ b/tests/e2e/specs/Task.spec.js @@ -15,6 +15,67 @@ function initializeTaskAndScreenIntercepts(method, url, response) { response.screen ); } + +function getTask(url, responseData) { + initializeTaskAndScreenIntercepts("GET", url, { + id: responseData.id, + advanceStatus: "completed", + component: "task-screen", + status: responseData.status, + allow_interstitial: responseData.allow_interstitial, + interstitial_screen: responseData.interstitial_screen, + screen: responseData.screen, + process_request: { + id: 1, + parent_request_id: responseData.parent_request_id, + status: responseData.status + }, + user_request_permission: responseData.user_request_permission + }); +} + +function getTasks(url, responseData = null) { + if (responseData) { + cy.intercept("GET", url, { + data: [ + { + id: responseData.taskId, + advanceStatus: "open", + process_id: 1, + process_request_id: responseData.process_request_id, + subprocess_request_id: 1, + status: responseData.status, + completed_at: null, + due_at: moment().add(1, "day").toISOString(), + due_notified: 0, + process_request: { + id: 1, + status: responseData.status + } + } + ] + }); + } else { + cy.intercept("GET", url, { data: [] }); + } +} + +function getActiveTasksUrl(requestId, includeUserFilter = true) { + const userFilter = includeUserFilter ? "user_id=1&" : ""; + + return `http://localhost:5173/api/1.1/tasks?${userFilter}status=ACTIVE&process_request_id=${requestId}&include_sub_tasks=1`; +} + +function mockInterstitialTaskLookups( + requestId, + { userTask = null, activeTasks = [] } = {} +) { + getTasks(getActiveTasksUrl(requestId), userTask); + cy.intercept("GET", getActiveTasksUrl(requestId, false), { + data: activeTasks + }); +} + describe("Task component", () => { it("In a webentry", () => { cy.visit("/?scenario=WebEntry", { @@ -517,10 +578,7 @@ describe("Task component", () => { responseDataTask1 ); - getTasks( - "http://localhost:5173/api/1.1/tasks?user_id=1&status=ACTIVE&process_request_id=1&include_sub_tasks=1", - null - ); + mockInterstitialTaskLookups(1); const responseDataTask2 = { status: "ACTIVE", @@ -712,9 +770,7 @@ describe("Task component", () => { responseDataTask1 ); - getTasks( - "http://localhost:5173/api/1.1/tasks?user_id=1&status=ACTIVE&process_request_id=1&include_sub_tasks=1" - ); + mockInterstitialTaskLookups(1); } ); @@ -726,6 +782,227 @@ describe("Task component", () => { cy.url().should("eq", "http://localhost:5173/requests/1"); }); + it("Interstitial should redirect to request when another active task exists for a different user", () => { + initializeTaskAndScreenIntercepts( + "GET", + "http://localhost:5173/api/1.1/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", + { + id: 1, + advanceStatus: "open", + component: "task-screen", + status: "TRIGGERED", + allow_interstitial: true, + interstitial_screen: InterstitialScreen.screens[0], + screen: SingleScreen.screens[0], + process_request: { + id: 1, + status: "ACTIVE" + } + } + ); + + mockInterstitialTaskLookups(1, { + activeTasks: [ + { + id: 2, + element_id: "node_2", + element_name: "FT-A", + element_type: "task", + status: "ACTIVE", + user_id: null, + process_request_id: 1, + advanceStatus: "open" + } + ] + }); + + cy.visit("/?scenario=TaskRedirect", {}); + + cy.wait(2000); + cy.url().should("eq", "http://localhost:5173/requests/1"); + }); + + it("Interstitial should load the next claimable self-service task from the user-filtered lookup", () => { + initializeTaskAndScreenIntercepts( + "GET", + "http://localhost:5173/api/1.1/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", + { + id: 1, + advanceStatus: "open", + component: "task-screen", + status: "TRIGGERED", + allow_interstitial: true, + interstitial_screen: InterstitialScreen.screens[0], + screen: SingleScreen.screens[0], + process_request: { + id: 1, + status: "ACTIVE" + } + } + ); + + cy.intercept("GET", getActiveTasksUrl(1), { + data: [ + { + id: 2, + advanceStatus: "open", + process_id: 1, + process_request_id: 1, + subprocess_request_id: 1, + status: "ACTIVE", + user_id: null, + is_self_service: 1, + self_service_groups: { + users: ["1"], + groups: [] + }, + completed_at: null, + due_at: moment().add(1, "day").toISOString(), + due_notified: 0, + process_request: { + id: 1, + status: "ACTIVE" + } + } + ] + }); + cy.intercept("GET", getActiveTasksUrl(1, false), { data: [] }); + + initializeTaskAndScreenIntercepts( + "GET", + "http://localhost:5173/api/1.1/tasks/2?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", + { + id: 2, + advanceStatus: "open", + component: "task-screen", + status: "ACTIVE", + screen: Screens.screens[0], + process_request: { + id: 1, + status: "ACTIVE" + }, + user_request_permission: [{ process_request_id: 1, allowed: true }] + } + ); + + cy.visit("/?scenario=TaskRedirect", {}); + + cy.wait(2000); + cy.get("[data-cy=screen-field-firstname]").should("be.visible"); + cy.get("[data-cy=screen-field-lastname]").should("be.visible"); + cy.url().should("eq", "http://localhost:5173/?scenario=TaskRedirect"); + }); + + it("Interstitial should load a claimable self-service task from redirectToTask", () => { + initializeTaskAndScreenIntercepts( + "GET", + "http://localhost:5173/api/1.1/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", + { + id: 1, + advanceStatus: "open", + component: "task-screen", + status: "TRIGGERED", + allow_interstitial: true, + interstitial_screen: InterstitialScreen.screens[0], + screen: SingleScreen.screens[0], + process_request: { + id: 1, + status: "ACTIVE" + } + } + ); + + mockInterstitialTaskLookups(1); + + initializeTaskAndScreenIntercepts( + "GET", + "http://localhost:5173/api/1.1/tasks/2?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", + { + id: 2, + advanceStatus: "open", + component: "task-screen", + status: "ACTIVE", + screen: Screens.screens[0], + process_request: { + id: 1, + status: "ACTIVE" + }, + user_request_permission: [{ process_request_id: 1, allowed: true }] + } + ); + + cy.visit("/?scenario=TaskRedirect", {}); + + cy.wait(2000); + cy.contains("Please wait").should("be.visible"); + cy.get("[data-cy=screen-field-firstname]").should("not.exist"); + + cy.socketEventNext("ProcessMaker\\Events\\RedirectTo", { + params: { + "0": { + nodeId: "node_2", + tokenId: 2, + userId: null, + userCanClaim: true + }, + activeTokens: [2] + }, + method: "redirectToTask" + }); + + cy.get("[data-cy=screen-field-firstname]").should("be.visible"); + cy.get("[data-cy=screen-field-lastname]").should("be.visible"); + cy.url().should("eq", "http://localhost:5173/?scenario=TaskRedirect"); + }); + + it("Interstitial should keep waiting when there are no active tasks yet", () => { + initializeTaskAndScreenIntercepts( + "GET", + "http://localhost:5173/api/1.1/tasks/1?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination", + { + id: 1, + advanceStatus: "open", + component: "task-screen", + screen: SingleScreen.screens[0], + process_request: { + id: 1, + status: "ACTIVE" + } + } + ); + + cy.visit("/?scenario=TaskRedirect", {}); + + cy.wait(2000); + cy.get(".form-group").find("button").click(); + + cy.intercept("PUT", "http://localhost:5173/api/1.1/tasks/1").then( + function () { + const responseDataTask1 = { + status: "CLOSED", + process_request_id: 1, + id: 1, + screen: SingleScreen.screens[0], + allow_interstitial: true, + interstitial_screen: InterstitialScreen.screens[0] + }; + + getTask( + `http://localhost:5173/api/1.1/tasks/${responseDataTask1.id}?include=data,user,draft,requestor,processRequest,component,screen,requestData,loopContext,bpmnTagName,interstitial,definition,nested,userRequestPermission,elementDestination`, + responseDataTask1 + ); + + mockInterstitialTaskLookups(1); + + cy.wait(2000); + cy.reload(); + } + ); + + cy.contains("Please wait").should("be.visible"); + cy.url().should("eq", "http://localhost:5173/?scenario=TaskRedirect"); + }); + /* DNAT = Display Next Assigned Task parentTask1 endEvent \_______childTask1_______/ @@ -776,9 +1053,7 @@ describe("Task component", () => { responseDataTask1 ); - getTasks( - "http://localhost:5173/api/1.1/tasks?user_id=1&status=ACTIVE&process_request_id=1&include_sub_tasks=1" - ); + mockInterstitialTaskLookups(1); cy.wait(2000); cy.reload(); @@ -966,46 +1241,3 @@ describe("Task component", () => { cy.get("[data-cy=screen-field-lastname]").should("not.exist"); }); }); - -function getTask(url, responseData) { - initializeTaskAndScreenIntercepts("GET", url, { - id: responseData.id, - advanceStatus: "completed", - component: "task-screen", - status: responseData.status, - allow_interstitial: responseData.allow_interstitial, - interstitial_screen: responseData.interstitial_screen, - screen: responseData.screen, - process_request: { - id: 1, - parent_request_id: responseData.parent_request_id, - status: responseData.status - }, - user_request_permission: responseData.user_request_permission - }); -} -function getTasks(url, responseData = null) { - if (responseData) { - cy.intercept("GET", url, { - data: [ - { - id: responseData.taskId, - advanceStatus: "open", - process_id: 1, - process_request_id: responseData.process_request_id, - subprocess_request_id: 1, - status: responseData.status, - completed_at: null, - due_at: moment().add(1, "day").toISOString(), - due_notified: 0, - process_request: { - id: 1, - status: responseData.status - } - } - ] - }); - } else { - cy.intercept("GET", url, { data: [] }); - } -}