diff --git a/CHANGELOG.md b/CHANGELOG.md index 453c0652..238755f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ # Unreleased +* Implemented editing and deletion of tasks (\#207). * Bump `word-wrap` from 1.2.3 to 1.2.5 (\#251). * Implemented A to B workflow execution (\#254). * Improved enable/disable button state (\#257). diff --git a/src/lib/common/component_utilities.js b/src/lib/common/component_utilities.js index 434f780a..883e231d 100644 --- a/src/lib/common/component_utilities.js +++ b/src/lib/common/component_utilities.js @@ -80,3 +80,25 @@ export function fieldHasValue(event) { const inputValue = event.target?.value || undefined; return inputValue !== undefined && inputValue !== ''; } + +export function getOnlyModifiedProperties(oldProperties, newProperties) { + const modifiedProperties = {}; + for (let key in newProperties) { + if (newProperties[key] !== oldProperties[key]) { + modifiedProperties[key] = newProperties[key]; + } + } + return modifiedProperties; +} + +export function unsetEmptyStrings(inputValues) { + const clearedValues = {}; + for (let key in inputValues) { + if (typeof(inputValues[key]) === 'string' && inputValues[key].trim() === '') { + clearedValues[key] = null; + } else { + clearedValues[key] = inputValues[key]; + } + } + return clearedValues; +} diff --git a/src/lib/components/tasks/TaskEditModal.svelte b/src/lib/components/tasks/TaskEditModal.svelte new file mode 100644 index 00000000..11b512d8 --- /dev/null +++ b/src/lib/components/tasks/TaskEditModal.svelte @@ -0,0 +1,164 @@ + + + diff --git a/src/lib/components/tasks/TaskInfoModal.svelte b/src/lib/components/tasks/TaskInfoModal.svelte index 278116ab..0cd48e86 100644 --- a/src/lib/components/tasks/TaskInfoModal.svelte +++ b/src/lib/components/tasks/TaskInfoModal.svelte @@ -1,6 +1,6 @@ diff --git a/src/lib/components/workflow/MetaPropertiesForm.svelte b/src/lib/components/workflow/MetaPropertiesForm.svelte index 98ad42a9..ec580400 100644 --- a/src/lib/components/workflow/MetaPropertiesForm.svelte +++ b/src/lib/components/workflow/MetaPropertiesForm.svelte @@ -10,6 +10,7 @@ import { updateFormEntry } from '$lib/components/workflow/task_form_utils'; import FormBuilder from '$lib/components/workflow/common/FormBuilder.svelte'; import StandardErrorAlert from '$lib/components/common/StandardErrorAlert.svelte'; + import { getOnlyModifiedProperties } from '$lib/common/component_utilities'; // Workflow id export let workflowId; @@ -29,11 +30,12 @@ async function handleEntryUpdate() { const projectId = $page.params.projectId; try { + const modifiedProperties = getOnlyModifiedProperties(originalMetaProperties, metaProperties); const updatedMetaProperties = await updateFormEntry( projectId, workflowId, taskId, - getOnlyModifiedProperties(), + modifiedProperties, 'meta' ); metaProperties = updatedMetaProperties.meta; @@ -51,16 +53,6 @@ }); } } - - function getOnlyModifiedProperties() { - const modifiedProperties = {}; - for (let key in metaProperties) { - if (metaProperties[key] !== originalMetaProperties[key]) { - modifiedProperties[key] = metaProperties[key]; - } - } - return modifiedProperties; - }
diff --git a/src/lib/server/api/v1/task_api.js b/src/lib/server/api/v1/task_api.js index b90384d9..46ec258a 100644 --- a/src/lib/server/api/v1/task_api.js +++ b/src/lib/server/api/v1/task_api.js @@ -1,5 +1,5 @@ import { FRACTAL_SERVER_HOST } from '$env/static/private'; -import { PostResourceException } from '$lib/common/errors.js'; +import { PostResourceException, responseError } from '$lib/common/errors.js'; /** * Fetches a list of tasks from the server @@ -42,6 +42,7 @@ export async function createTask(fetch, formData) { const requestData = { name: formData.get('name'), command: formData.get('command'), + version: formData.get('version'), source: formData.get('source'), input_type: formData.get('input_type'), output_type: formData.get('output_type') @@ -136,3 +137,56 @@ export async function taskCollectionStatus(fetch, taskId) { throw new Error('Unable to fetch collection operation status'); } + + +/** + * Deletes a task from the server + * @param fetch + * @param taskId + * @returns {Promise<*>} + */ +export async function deleteTask(fetch, taskId) { + const response = await fetch( + FRACTAL_SERVER_HOST + `/api/v1/task/${taskId}`, + { + method: 'DELETE', + credentials: 'include', + mode: 'cors' + } + ); + + if (response.ok) { + return new Response(null, { status: response.status }); + } + + await responseError(response); +} + + +/** + * Edits a task on the server + * @param fetch + * @param taskId + * @returns {Promise<*>} + */ +export async function editTask(fetch, taskId, task) { + const headers = new Headers(); + headers.append('Content-Type', 'application/json'); + + const response = await fetch( + FRACTAL_SERVER_HOST + `/api/v1/task/${taskId}`, + { + method: 'PATCH', + credentials: 'include', + mode: 'cors', + headers, + body: JSON.stringify(task) + } + ); + + if (response.ok) { + return new Response(null, { status: response.status }); + } + + await responseError(response); +} \ No newline at end of file diff --git a/src/lib/stores/taskStores.js b/src/lib/stores/taskStores.js index 6f32673e..3113288e 100644 --- a/src/lib/stores/taskStores.js +++ b/src/lib/stores/taskStores.js @@ -1,4 +1,5 @@ import { writable } from 'svelte/store'; -export const taskModal = writable(undefined); +export const taskStore = writable(undefined); +export const originalTaskStore = writable(undefined); export const modalTaskCollectionId = writable(undefined); diff --git a/src/routes/tasks/+page.svelte b/src/routes/tasks/+page.svelte index db628c32..ee9aafcb 100644 --- a/src/routes/tasks/+page.svelte +++ b/src/routes/tasks/+page.svelte @@ -4,7 +4,8 @@ import { enhance } from '$app/forms'; import { orderTasksByOwnerThenByNameThenByVersion } from '$lib/common/component_utilities.js'; import { collectTaskErrorStore } from '$lib/stores/errorStores'; - import { taskModal as taskModalStore } from '$lib/stores/taskStores'; + import { originalTaskStore, taskStore } from '$lib/stores/taskStores'; + import TaskEditModal from '$lib/components/tasks/TaskEditModal.svelte'; import TaskInfoModal from '$lib/components/tasks/TaskInfoModal.svelte'; import TaskCollection from '$lib/components/tasks/TaskCollection.svelte'; import StandardErrorAlert from '$lib/components/common/StandardErrorAlert.svelte'; @@ -39,7 +40,19 @@ function setTaskModal(event) { const taskId = event.currentTarget.getAttribute('data-fc-task'); const task = tasks.find((t) => t.id == taskId); - taskModalStore.set(task); + taskStore.set(task); + originalTaskStore.set({ ...task }); + } + + // Updates the tasks list after a task is edited in the modal + async function updateEditedTask(editedTask) { + tasks = tasks.filter(t => { + if (t.id === editedTask.id) { + return editedTask; + } else { + return t; + } + }); } async function reloadTaskList() { @@ -64,6 +77,26 @@ } }; } + + async function handleDeleteTask(taskId) { + const response = await fetch('/tasks/' + taskId, { + method: 'DELETE', + credentials: 'include' + }); + + if (response.ok) { + console.log('Task deleted successfully'); + tasks = tasks.filter((t) => t.id !== taskId); + } else { + console.error('Error deleting the task'); + new StandardErrorAlert({ + target: document.getElementById('taskErrorAlert'), + props: { + error: await response.json() + } + }); + } + }
@@ -129,6 +162,12 @@
+
+
+
Version
+ +
+
@@ -155,6 +194,7 @@

Task List

+ - +
@@ -178,7 +218,7 @@ {#each tasks as task}
{task.name}{task.version}{task.version || "-"} {task.owner || "–"} - + @@ -205,3 +256,4 @@ + diff --git a/src/routes/tasks/[taskId]/+server.js b/src/routes/tasks/[taskId]/+server.js new file mode 100644 index 00000000..fed0116b --- /dev/null +++ b/src/routes/tasks/[taskId]/+server.js @@ -0,0 +1,23 @@ +import { deleteTask, editTask } from '$lib/server/api/v1/task_api'; + +export async function PATCH({ fetch, url, request }) { + console.log('Edit task request'); + + const parts = url.pathname.split('/'); + const taskId = parts[parts.length - 1]; + console.log('Task to edit: ' + taskId); + + const taskData = await request.json(); + + return await editTask(fetch, taskId, taskData); +} + +export async function DELETE({ fetch, url }) { + console.log('Delete task request'); + + const parts = url.pathname.split('/'); + const taskId = parts[parts.length - 1]; + console.log('Task to delete: ' + taskId); + + return await deleteTask(fetch, taskId); +}