Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions public/locales/de-DE/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
},
"nav": {
"tasks": "Aufgaben",
"task-dashboard": "Offene Aufgaben",
"processes": "Prozesse",
"decisions": "Entscheidungen",
"deployments": "Deployments",
Expand Down Expand Up @@ -73,6 +74,7 @@
"pages": {
"dashboard": "Dashboard",
"tasks": "Aufgaben",
"task-dashboard": "Dashboard für offene Aufgaben",
"processes": "Prozesse",
"decisions": "Entscheidungen",
"deployments": "Deployments",
Expand Down Expand Up @@ -241,8 +243,10 @@
"assignee": "assignee",
"assigneeLike": "assigneeLike",
"candidateGroup": "candidateGroup",
"includeAssignedTasks": "includeAssignedTasks",
"candidateUser": "candidateUser",
"involvedUser": "involvedUser",
"assigned": "assigned",
"unassigned": "unassigned",
"processDefinitionKey": "processDefinitionKey",
"processDefinitionName": "processDefinitionName",
Expand All @@ -266,6 +270,27 @@
"suspended": "suspended"
}
},
"task_dashboard": {
"title": "Dashboard für offene Aufgaben",
"open-tasklist": "Tasklist öffnen",
"assignment-by-type": "Zuordnung nach Typ",
"assignment-by-group": "Zuordnung nach Gruppe",
"summary": {
"open": "offene Aufgaben",
"assigned": "zugewiesene Aufgaben",
"unassigned": "nicht zugewiesene Aufgaben"
},
"groups": {
"group": "Gruppe",
"open-tasks": "Offene Aufgaben",
"empty": "Keine offenen Aufgaben werden Gruppen angeboten."
},
"search": {
"title": "Aufgaben suchen",
"refresh": "Aktualisieren",
"empty": "Keine Aufgaben passen zu dieser Suche."
}
},
"processes": {
"title": "Prozessdefinitionen",
"version": "Version",
Expand Down
25 changes: 25 additions & 0 deletions public/locales/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
},
"nav": {
"tasks": "Tasks",
"task-dashboard": "Open Tasks",
"processes": "Processes",
"decisions": "Decisions",
"deployments": "Deployments",
Expand Down Expand Up @@ -73,6 +74,7 @@
"pages": {
"dashboard": "Dashboard",
"tasks": "Tasks",
"task-dashboard": "Open Tasks Dashboard",
"processes": "Processes",
"decisions": "Decisions",
"deployments": "Deployments",
Expand Down Expand Up @@ -241,8 +243,10 @@
"assignee": "assignee",
"assigneeLike": "assigneeLike",
"candidateGroup": "candidateGroup",
"includeAssignedTasks": "includeAssignedTasks",
"candidateUser": "candidateUser",
"involvedUser": "involvedUser",
"assigned": "assigned",
"unassigned": "unassigned",
"processDefinitionKey": "processDefinitionKey",
"processDefinitionName": "processDefinitionName",
Expand All @@ -266,6 +270,27 @@
"suspended": "suspended"
}
},
"task_dashboard": {
"title": "Open Tasks Dashboard",
"open-tasklist": "Open Tasklist",
"assignment-by-type": "Assignments by type",
"assignment-by-group": "Assignments by group",
"summary": {
"open": "open tasks",
"assigned": "assigned tasks",
"unassigned": "unassigned tasks"
},
"groups": {
"group": "Group",
"open-tasks": "Open tasks",
"empty": "No open tasks are offered to groups."
},
"search": {
"title": "Search Tasks",
"refresh": "Refresh",
"empty": "No tasks match this search."
}
},
"processes": {
"title": "Process Definitions",
"version": "Version",
Expand Down
154 changes: 136 additions & 18 deletions src/api/resources/task.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
GET_TEXT,
POST,
PUT,
get_auth_header,
get_credentials,
} from "../helper.jsx";
import engine_rest from "../engine_rest.jsx";
Expand Down Expand Up @@ -121,7 +122,14 @@
return tasks;
};

const get_tasks = (state, sort_key = "name", sort_order = "asc", firstResult = 0, maxResults = 3, filter = {}) => {
const get_tasks = (
state,
sort_key = "name",
sort_order = "asc",
firstResult = 0,
maxResults = 3,
filter = {},
) => {
const prev = state.api.task.list.value;
state.api.task.list.value = {
status: RESPONSE_STATE.LOADING,
Expand All @@ -142,32 +150,135 @@
...filter,
});

fetch(
`${_url_engine_rest(state)}/task?${params}`,
{ headers },
)
fetch(`${_url_engine_rest(state)}/task?${params}`, { headers })
.then((response) =>
response.ok ? response.json() : Promise.reject(response),
)
.then((tasks) => tasks_with_process_definitions(tasks, state))
.then(
(json) => {
const existing = firstResult > 0 ? (prev?.data ?? []) : [];
const existingIds = new Set(existing.map((t) => t.id));
const newTasks = json.filter((t) => !existingIds.has(t.id));
state.api.task.list.value = {
status: RESPONSE_STATE.SUCCESS,
data: [...existing, ...newTasks],
hasMore: json.length === maxResults,
};
},
)
.then((json) => {
const existing = firstResult > 0 ? (prev?.data ?? []) : [];
const existingIds = new Set(existing.map((t) => t.id));
const newTasks = json.filter((t) => !existingIds.has(t.id));
state.api.task.list.value = {
status: RESPONSE_STATE.SUCCESS,
data: [...existing, ...newTasks],
hasMore: json.length === maxResults,
};
})
.catch(
(error) =>
(state.api.task.list.value = { status: RESPONSE_STATE.ERROR, error }),
);
};

const fetch_task_count = async (state, filter = {}) => {
const headers = new Headers();
headers.set("Authorization", get_auth_header(state));

const params = new URLSearchParams();
for (const [key, value] of Object.entries(filter)) {
if (value !== null && value !== undefined && value !== "")
params.set(key, String(value));
}

const query = params.toString(),
response = await fetch(
`${_url_engine_rest(state)}/task/count${query ? `?${query}` : ""}`,

Check warning on line 186 in src/api/resources/task.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not use nested template literals.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ7RjXIx6EJ9mH8oGgRN&open=AZ7RjXIx6EJ9mH8oGgRN&pullRequest=60
{ headers },
),
json = await (response.ok ? response.json() : Promise.reject(response));

Check warning on line 189 in src/api/resources/task.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Expected the Promise rejection reason to be an Error.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ7RjXIx6EJ9mH8oGgRO&open=AZ7RjXIx6EJ9mH8oGgRO&pullRequest=60

return json.count ?? 0;
};

const get_task_dashboard_summary = async (state, groups = []) => {
const signal = state.api.task.dashboard.summary;
signal.value = {
status: RESPONSE_STATE.LOADING,
data: signal.peek?.()?.data,
};

try {
const [total, assigned, unassigned, group_counts] = await Promise.all([
fetch_task_count(state),
fetch_task_count(state, { assigned: true }),
fetch_task_count(state, { unassigned: true }),
Promise.all(
groups.map(async (group) => ({
id: group.id,
name: group.name ?? group.id,
count: await fetch_task_count(state, {
candidateGroup: group.id,
includeAssignedTasks: true,
}),
})),
),
]);

signal.value = {
status: RESPONSE_STATE.SUCCESS,
data: {
total,
assigned,
unassigned,
groups: group_counts.filter((group) => group.count > 0),
},
};
} catch (error) {
signal.value = { status: RESPONSE_STATE.ERROR, error };
}
};

const get_task_dashboard_results = async (
state,
filter = {},
sort_key = "name",
sort_order = "asc",
firstResult = 0,
maxResults = 20,
) => {
const signal = state.api.task.dashboard.results,
prev = signal.peek?.();
signal.value = {
status: RESPONSE_STATE.LOADING,
data: prev?.data,
hasMore: prev?.hasMore,
};

const headers = new Headers();
headers.set("Authorization", get_auth_header(state));

const params = new URLSearchParams({
sortBy: sort_key,
sortOrder: sort_order,
firstResult,
maxResults,
});
for (const [key, value] of Object.entries(filter)) {
if (value !== null && value !== undefined && value !== "")
params.set(key, String(value));
}

try {
const response = await fetch(`${_url_engine_rest(state)}/task?${params}`, {
headers,
}),
tasks = await (response.ok ? response.json() : Promise.reject(response)),

Check warning on line 266 in src/api/resources/task.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Expected the Promise rejection reason to be an Error.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ7RjXIx6EJ9mH8oGgRP&open=AZ7RjXIx6EJ9mH8oGgRP&pullRequest=60
enriched = await tasks_with_process_definitions(tasks, state),
existing = firstResult > 0 ? (prev?.data ?? []) : [],
existingIds = new Set(existing.map((t) => t.id)),
fresh = enriched.filter((t) => !existingIds.has(t.id));

signal.value = {
status: RESPONSE_STATE.SUCCESS,
data: [...existing, ...fresh],
hasMore: enriched.length === maxResults,
};
} catch (error) {
signal.value = { status: RESPONSE_STATE.ERROR, error };
}
};

const get_task_process_definitions = (state, ids) =>
fetch(
`${_url_engine_rest(state)}/process-definition?processDefinitionIdIn=${ids}`,
Expand All @@ -190,10 +301,17 @@
);

const post_task_form = (state, task_id, data) =>
POST(`/task/${task_id}/submit-form`, { variables: data, withVariablesInReturn: true, }, state, state.api.task.submit_form );
POST(
`/task/${task_id}/submit-form`,
{ variables: data, withVariablesInReturn: true },
state,
state.api.task.submit_form,
);

const task = {
get_tasks,
get_task_dashboard_summary,
get_task_dashboard_results,
get_task,
update_task,
get_task_form,
Expand Down
76 changes: 76 additions & 0 deletions src/api/resources/task.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,5 +234,81 @@ describe("api/resources/task", () => {
);
expect(result).toEqual([{ id: "d1" }]);
});

it("get_task_dashboard_summary counts all, assigned, unassigned and group tasks", async () => {
fetchMock
.mockResolvedValueOnce({ ok: true, json: async () => ({ count: 9 }) })
.mockResolvedValueOnce({ ok: true, json: async () => ({ count: 4 }) })
.mockResolvedValueOnce({ ok: true, json: async () => ({ count: 5 }) })
.mockResolvedValueOnce({ ok: true, json: async () => ({ count: 3 }) })
.mockResolvedValueOnce({ ok: true, json: async () => ({ count: 0 }) });

await task.get_task_dashboard_summary(state, [
{ id: "sales", name: "Sales" },
{ id: "empty", name: "Empty" },
]);

const urls = fetchMock.mock.calls.map(([url]) => url);
expect(urls[0]).toContain("/task/count");
expect(urls[1]).toContain("assigned=true");
expect(urls[2]).toContain("unassigned=true");
expect(urls[3]).toContain("candidateGroup=sales");
expect(urls[3]).toContain("includeAssignedTasks=true");
expect(state.api.task.dashboard.summary.value).toEqual({
status: RESPONSE_STATE.SUCCESS,
data: {
total: 9,
assigned: 4,
unassigned: 5,
groups: [{ id: "sales", name: "Sales", count: 3 }],
},
});
});

it("get_task_dashboard_results queries tasks into the dashboard result signal", async () => {
fetchMock
.mockResolvedValueOnce({
ok: true,
json: async () => [
{ id: "t1", name: "Review", processDefinitionId: "pd1" },
],
})
.mockResolvedValueOnce({
ok: true,
json: async () => [{ id: "pd1", name: "Invoice", version: 2 }],
});

await task.get_task_dashboard_results(
state,
{ candidateGroup: "sales", includeAssignedTasks: true },
"created",
"desc",
0,
20,
);

const url = fetchMock.mock.calls[0][0];
expect(url).toContain("/task?");
expect(url).toContain("sortBy=created");
expect(url).toContain("sortOrder=desc");
expect(url).toContain("candidateGroup=sales");
expect(url).toContain("includeAssignedTasks=true");
expect(fetchMock.mock.calls[1][0]).toContain(
"/process-definition?processDefinitionIdIn=pd1",
);
expect(state.api.task.dashboard.results.value).toEqual({
status: RESPONSE_STATE.SUCCESS,
data: [
{
id: "t1",
name: "Review",
processDefinitionId: "pd1",
definitionName: "Invoice",
definitionVersion: 2,
},
],
hasMore: false,
});
});
});
});
Loading