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/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 @@
+