Skip to content

Commit

Permalink
Merge pull request #262 from fractal-analytics-platform/172-add-delet…
Browse files Browse the repository at this point in the history
…e-task-feature

Implemented editing and deletion of tasks
  • Loading branch information
zonia3000 authored Aug 25, 2023
2 parents b625d06 + eaa2025 commit eb00f1f
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
22 changes: 22 additions & 0 deletions src/lib/common/component_utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
164 changes: 164 additions & 0 deletions src/lib/components/tasks/TaskEditModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<script>
import { originalTaskStore, taskStore } from '$lib/stores/taskStores';
import StandardErrorAlert from '$lib/components/common/StandardErrorAlert.svelte';
import { getOnlyModifiedProperties, unsetEmptyStrings } from '$lib/common/component_utilities';
export let updateEditedTask;
$: task = $taskStore;
$: originalTask = $originalTaskStore;
$: updateEnabled = task && task.name && task.command && task.input_type && task.output_type;
async function handleEditTask() {
let taskProperties = unsetEmptyStrings(task);
taskProperties = getOnlyModifiedProperties(originalTask, taskProperties);
const response = await fetch('/tasks/' + task.id, {
method: 'PATCH',
credentials: 'include',
body: JSON.stringify(taskProperties)
});
if (response.ok) {
console.log('Task updated successfully');
updateEditedTask(task);
// eslint-disable-next-line no-undef
const modal = bootstrap.Modal.getInstance(
document.getElementById('taskEditModal')
);
modal.hide();
} else {
new StandardErrorAlert({
target: document.getElementById('editTaskErrorAlert'),
props: {
error: await response.json()
}
});
}
}
async function undoChangesAndClose() {
for (let key in originalTask) {
task[key] = originalTask[key];
}
// eslint-disable-next-line no-undef
const modal = bootstrap.Modal.getInstance(
document.getElementById('taskEditModal')
);
modal.hide();
}
</script>

<div class="modal modal-xl" id="taskEditModal" data-bs-backdrop="static">
<div class="modal-dialog">
{#if task}
<div class="modal-content">
<div class="modal-header">
<h1 class="h5 modal-title">Task {task.name}</h1>
</div>
<div class="modal-body">
<div class="row mb-3">
<div class="col-12">
<p class="lead">Task properties</p>

<span id="editTaskErrorAlert"></span>

<div class="mb-2 row">
<label for="taskName" class="col-2 col-form-label text-end">Name</label>
<div class="col-10">
<input id="taskName" type="text" bind:value={task.name} class="form-control" class:is-invalid={!task.name}>
{#if !task.name}
<div class="invalid-feedback">Required field</div>
{/if}
</div>
</div>

<div class="mb-2 row">
<label for="version" class="col-2 col-form-label text-end">Version</label>
<div class="col-10">
<input id="version" type="text" bind:value={task.version} class="form-control">
</div>
</div>

<div class="mb-2 row">
<label for="owner" class="col-2 col-form-label text-end">Owner</label>
<div class="col-10">
<input id="owner" type="text" bind:value={task.owner} disabled class="form-control">
</div>
</div>

<div class="mb-2 row">
<label for="command" class="col-2 col-form-label text-end">Command</label>
<div class="col-10">
<input id="command" type="text" bind:value={task.command} class="form-control" class:is-invalid={!task.command}>
{#if !task.command}
<div class="invalid-feedback">Required field</div>
{/if}
</div>
</div>

<div class="mb-2 row">
<label for="source" class="col-2 col-form-label text-end">Source</label>
<div class="col-10">
<input id="source" type="text" bind:value={task.source} disabled class="form-control">
</div>
</div>

<div class="mb-2 row">
<label for="inputType" class="col-2 col-form-label text-end">Input Type</label>
<div class="col-10">
<input id="inputType" type="text" bind:value={task.input_type} class="form-control" class:is-invalid={!task.input_type}>
{#if !task.input_type}
<div class="invalid-feedback">Required field</div>
{/if}
</div>
</div>

<div class="mb-2 row">
<label for="outputType" class="col-2 col-form-label text-end">Output Type</label>
<div class="col-10">
<input id="outputType" type="text" bind:value={task.output_type} class="form-control" class:is-invalid={!task.output_type}>
{#if !task.output_type}
<div class="invalid-feedback">Required field</div>
{/if}
</div>
</div>

<div class="mb-2 row">
<label for="argsSchemaVersion" class="col-2 col-form-label text-end">Args Schema Version</label>
<div class="col-10">
<input id="ar$gsSchemaVersion" type="text" bind:value={task.args_schema_version} disabled class="form-control">
</div>
</div>

<div class="mb-2 row">
<label for="argsSchema" class="col-2 col-form-label text-end">Args Schema</label>
<div class="col-10">
<textarea name="argsSchema" value={JSON.stringify(task.args_schema, null, 2)} disabled class="form-control" rows="10"></textarea>
</div>
</div>

<div class="mb-2 row">
<label for="docsLink" class="col-2 col-form-label text-end">Docs Link</label>
<div class="col-10">
<input id="docsLink" type="text" bind:value={task.docs_link} disabled class="form-control">
</div>
</div>

<div class="mb-2 row">
<label for="docsInfo" class="col-2 col-form-label text-end">Docs Info</label>
<div class="col-10">
<textarea id="docsInfo" type="text" bind:value={task.docs_info} disabled class="form-control" rows="5"></textarea>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" on:click={handleEditTask} disabled={!updateEnabled}>Update</button>
<button class="btn btn-secondary" on:click={undoChangesAndClose}>Close</button>
</div>
</div>
{/if}
</div>
</div>
32 changes: 27 additions & 5 deletions src/lib/components/tasks/TaskInfoModal.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script>
// TaskInfoModal component
import { taskModal } from '$lib/stores/taskStores';
import { taskStore } from '$lib/stores/taskStores';
// Task to be displayed
// let task = {}
Expand All @@ -11,7 +11,7 @@
// taskModal.subscribe( taskId => {
// project =
// })
$: task = $taskModal;
$: task = $taskStore;
</script>

<div class="modal modal-xl" id="taskInfoModal">
Expand All @@ -29,9 +29,9 @@
<li class="list-group-item list-group-item-light fw-bold">Name</li>
<li class="list-group-item">{task?.name}</li>
<li class="list-group-item list-group-item-light fw-bold">Version</li>
<li class="list-group-item">{task?.version}</li>
<li class="list-group-item">{task?.version || '-'}</li>
<li class="list-group-item list-group-item-light fw-bold">Owner</li>
<li class="list-group-item">{task?.owner}</li>
<li class="list-group-item">{task?.owner || '-'}</li>
<li class="list-group-item list-group-item-light fw-bold">Command</li>
<li class='list-group-item'><code>{task?.command}</code></li>
<li class='list-group-item list-group-item-light fw-bold'>Source</li>
Expand All @@ -45,12 +45,34 @@
<pre>{task?.output_type}</pre>
</li>
<li class='list-group-item list-group-item-light fw-bold'>Args Schema Version</li>
<li class='list-group-item'>{task?.args_schema_version}</li>
<li class='list-group-item'>{task?.args_schema_version || '-'}</li>
<li class='list-group-item list-group-item-light fw-bold'>Args Schema</li>
<li class='list-group-item'>
{#if task?.args_schema}
<code>
<pre>{JSON.stringify(task?.args_schema, null, 2)}</pre>
</code>
{:else}
-
{/if}
</li>
<li class='list-group-item list-group-item-light fw-bold'>Docs Link</li>
<li class='list-group-item'>
{#if task?.docs_link}
<a href="{task?.docs_link}" target="_blank">{task?.docs_link}</a>
{:else}
-
{/if}
</li>
<li class='list-group-item list-group-item-light fw-bold'>Docs Info</li>
<li class='list-group-item'>
{#if task?.docs_info}
<code>
<pre>{task?.docs_info}</pre>
</code>
{:else}
-
{/if}
</li>
</ul>
</div>
Expand Down
14 changes: 3 additions & 11 deletions src/lib/components/workflow/MetaPropertiesForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -51,16 +53,6 @@
});
}
}
function getOnlyModifiedProperties() {
const modifiedProperties = {};
for (let key in metaProperties) {
if (metaProperties[key] !== originalMetaProperties[key]) {
modifiedProperties[key] = metaProperties[key];
}
}
return modifiedProperties;
}
</script>

<div>
Expand Down
56 changes: 55 additions & 1 deletion src/lib/server/api/v1/task_api.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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);
}
3 changes: 2 additions & 1 deletion src/lib/stores/taskStores.js
Original file line number Diff line number Diff line change
@@ -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);
Loading

0 comments on commit eb00f1f

Please sign in to comment.