diff --git a/frontend/javascripts/admin/task/task_create_form_view.tsx b/frontend/javascripts/admin/task/task_create_form_view.tsx index d5ce513382..6ab877301b 100644 --- a/frontend/javascripts/admin/task/task_create_form_view.tsx +++ b/frontend/javascripts/admin/task/task_create_form_view.tsx @@ -1,4 +1,4 @@ -import { InboxOutlined, ReloadOutlined, WarningOutlined } from "@ant-design/icons"; +import { DownloadOutlined, InboxOutlined, ReloadOutlined } from "@ant-design/icons"; import { createTaskFromNML, createTasks, getTask, updateTask } from "admin/api/tasks"; import { getActiveDatasetsOfMyOrganization, @@ -15,11 +15,13 @@ import type { } from "admin/task/task_create_bulk_view"; import { NUM_TASKS_PER_BATCH, normalizeFileEvent } from "admin/task/task_create_bulk_view"; import { + Alert, App, Button, Card, Col, Divider, + Flex, Form, Input, InputNumber, @@ -29,6 +31,7 @@ import { Select, Spin, Tooltip, + Typography, Upload, type UploadFile, } from "antd"; @@ -37,9 +40,13 @@ import { AsyncButton } from "components/async_clickables"; import { formatDateInLocalTimeZone } from "components/formatted_date"; import SelectExperienceDomain from "components/select_experience_domain"; import { saveAs } from "file-saver"; -import { coalesce, tryToAwaitPromise } from "libs/utils"; +import { coalesce, pluralize, tryToAwaitPromise } from "libs/utils"; import { Vector3Input, Vector6Input } from "libs/vector_input"; -import _ from "lodash"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import omit from "lodash/omit"; +import omitBy from "lodash/omitBy"; +import uniq from "lodash/uniq"; import messages from "messages"; import React, { useEffect, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; @@ -108,7 +115,7 @@ export function downloadTasksAsCSV(tasks: Array) { const lastCreationTime = Math.max(...tasks.map((task) => task.created)); const currentDateAsString = formatDateInLocalTimeZone(lastCreationTime, "YYYY-MM-DD_HH-mm"); - const allProjectNames = _.uniq(tasks.map((task) => task.projectName)).join("_"); + const allProjectNames = uniq(tasks.map((task) => task.projectName)).join("_"); const allTasksAsStrings = tasks.map((task) => taskToText(task)).join("\n"); const csv = [TASK_CSV_HEADER, allTasksAsStrings].join("\n"); @@ -128,9 +135,7 @@ export function handleTaskCreationResponse( const successfulTasks: APITask[] = []; const failedTasks: string[] = []; let teamName: string | null = null; - const subHeadingStyle: React.CSSProperties = { - fontWeight: "bold", - }; + const displayResultsStyle: React.CSSProperties = { maxHeight: 300, overflow: "auto", @@ -148,7 +153,7 @@ export function handleTaskCreationResponse( } }); - const allProjectNames = _.uniq(successfulTasks.map((task) => task.projectName)); + const allProjectNames = uniq(successfulTasks.map((task) => task.projectName)); if (allProjectNames.length > 1) { warnings.push( @@ -159,62 +164,54 @@ export function handleTaskCreationResponse( const warningsContent = warnings.length > 0 ? (
-
- {" "} - There were warnings during task creation: -
-
- {warnings.join("\n")} -
+
) : null; const failedTasksAsString = failedTasks.join(""); const successfulTasksContent = successfulTasks.length <= maxDisplayedTasksCount ? ( -
-        taskId,filename,position
-        
- {successfulTasks.map((task) => taskToShortText(task)).join("\n")} -
+ +
+          taskId,filename,position
+          
+ {successfulTasks.map((task) => taskToShortText(task)).join("\n")} +
+
) : ( "Too many tasks to show, please use the CSV download above for a full list." ); const failedTasksContent = failedTasks.length <= maxDisplayedTasksCount ? ( -
{failedTasksAsString}
+ +
{failedTasksAsString}
+
) : ( "Too many failed tasks to show, please use the CSV download for a full list." ); - const successPlural = successfulTasks.length === 1 ? "" : "s"; - const warningsPlural = warnings.length === 1 ? "" : "s"; + modal.info({ - title: `${successfulTasks.length} task${successPlural} successfully created, ${failedTasks.length} failed. ${warnings.length} warning${warningsPlural}.`, + title: `${successfulTasks.length} ${pluralize("task", successfulTasks.length)} successfully created, ${failedTasks.length} ${pluralize("task", failedTasks.length)} failed. ${warnings.length} ${pluralize("warning", warnings.length)}.`, content: (
{warningsContent} {successfulTasks.length > 0 ? (
-
- -
-
Successful Tasks:
+ + Successful Tasks:
{successfulTasksContent}
) : null} @@ -222,10 +219,9 @@ export function handleTaskCreationResponse(
-
@@ -236,14 +232,13 @@ export function handleTaskCreationResponse( }); saveAs(blob, "failed-tasks.csv"); }} + icon={} > Download failed task info as CSV -
-
Failed Tasks:
+ + Failed Tasks:
{failedTasksContent}
-
-
) : null} @@ -255,18 +250,9 @@ export function handleTaskCreationResponse( export function CreateResourceButton({ text, link }: { text: string; link: string }) { return ( - + ); @@ -280,9 +266,9 @@ export function ReloadResourceButton({ onReload: () => Promise; }) { return ( - + - } onClick={onReload} /> + } onClick={onReload} /> ); @@ -349,7 +335,7 @@ function TaskCreateFormView() { pendingInstances: task.status.pending, }); - const validFormValues = _.omitBy(defaultValues, _.isNil); + const validFormValues = omitBy(defaultValues, isNil); // The task type is not needed for the form and leads to antd errors if it contains null values const { type, ...neededFormValues } = validFormValues; @@ -374,7 +360,7 @@ function TaskCreateFormView() { if (taskId != null) { // either update an existing task const newTask = { - ..._.omit(formValues, "nmlFiles", "baseAnnotation"), + ...omit(formValues, "nmlFiles", "baseAnnotation"), boundingBox, }; const confirmedTask = await updateTask(taskId, newTask); @@ -397,7 +383,7 @@ function TaskCreateFormView() { const batchOfNmls = nmlFiles.slice(i, i + NUM_TASKS_PER_BATCH); const newTask: NewNmlTask = { - ..._.omit(formValues, "baseAnnotation"), + ...omit(formValues, "baseAnnotation"), boundingBox, }; const response = await createTaskFromNML(newTask, batchOfNmls); @@ -414,7 +400,7 @@ function TaskCreateFormView() { : formValues.baseAnnotation; const newTask = { - ..._.omit(formValues, "nmlFiles", "baseAnnotation"), + ...omit(formValues, "nmlFiles", "baseAnnotation"), boundingBox, baseAnnotation, }; @@ -426,7 +412,7 @@ function TaskCreateFormView() { handleTaskCreationResponse(modal, { tasks: taskResponses, - warnings: _.uniq(warnings), + warnings: uniq(warnings), }); } finally { setIsUploading(false); @@ -503,7 +489,7 @@ function TaskCreateFormView() { if ( taskResponse?.datasetId != null && - _.isEqual(taskResponse.status, { + isEqual(taskResponse.status, { pending: 0, active: 0, finished: 1, @@ -529,7 +515,7 @@ function TaskCreateFormView() { ) : null} - + - + - + - + - +