Skip to content

Commit

Permalink
Merge pull request #11 from DeNA/development
Browse files Browse the repository at this point in the history
Release 202107-1
  • Loading branch information
jonatan-alama authored Jul 6, 2021
2 parents 57dee39 + f639602 commit ba1d12e
Show file tree
Hide file tree
Showing 14 changed files with 493 additions and 6 deletions.
8 changes: 8 additions & 0 deletions packages/nota-client/src/components/admin/AdminProjectTask.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Loading from "../Loading";
import AdminProjectTaskAssignment from "./AdminProjectTaskAssignment";
import AdminProjectTaskExport from "./AdminProjectTaskExport";
import AdminProjectTaskFetch from "./AdminProjectTaskFetch";
import AdminProjectTaskMaintenance from "./AdminProjectTaskMaintenance";

export function AdminProjectTask({ resource, project, loading, doGet }) {
const { task, assignableUsers } = resource || {
Expand Down Expand Up @@ -240,6 +241,13 @@ export function AdminProjectTask({ resource, project, loading, doGet }) {
fetchJobs={task.fetchJobs}
reload={handleReload}
/>
<br />
<AdminProjectTaskMaintenance
projectId={project.id}
task={task}
fetchJobs={task.maintenanceJobs}
reload={handleReload}
/>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import React, { useRef } from "react";
import { Button, Card, Col, ListGroup, Nav, Row, Modal } from "react-bootstrap";
import { parseDate } from "../../lib/utils";
import { performTaskMaintenance } from "../../lib/api";
import { JobTask } from "../../lib/models";
import useInputForm, { string } from "../../lib/useInputForm";
import { Form } from "react-bootstrap";

function AdminProjectTaskMaintenance({
projectId,
task,
fetchJobs = [],
reload
}) {
const [showNewMaintenanceModal, setShowNewMaintenanceModal] = React.useState(
false
);
const annotationNamesSelect = useRef();

const handlePerformMaintenance = async function(values) {
const maintenance = {
type: "STATUS_RESET",
options: {
annotations: ["UNDO_ALL", "UNDO_BY_NAME"].includes(values.annotations),
annotationConditions: {},
taskItems: ["UNDO_ONGOING", "UNDO_ALL"].includes(values.taskItems),
taskItemConditions: {
onlyWithOngoing: values.taskItems === "UNDO_ONGOING"
},
taskAssignments: ["UNDO_ONGOING", "UNDO_ALL"].includes(
values.taskAssignments
),
taskAssignmentConditions: {
onlyWithOngoing: values.taskAssignments === "UNDO_ONGOING"
}
}
};

if (values.annotations === "UNDO_BY_NAME") {
if (!values.annotationNames) {
return;
}

maintenance.options.annotationConditions.name = values.annotationNames.split(
","
);
}

performTaskMaintenance({ projectId, taskId: task.id, maintenance });
setShowNewMaintenanceModal(false);
reload();
};
const handleClickNew = async function() {
setShowNewMaintenanceModal(true);
};
const handleCloseModal = function() {
setShowNewMaintenanceModal(false);
};

const formSchema = {
annotations: string().required(),
annotationNames: string(),
taskItems: string().required(),
taskAssignments: string().required()
};
const [
{ values, touched, errors },
handleChange,
handleSubmit
] = useInputForm(handlePerformMaintenance, formSchema, {
annotations: "NO_ACTION",
taskItems: "UNDO_ONGOING",
taskAssignments: "NO_ACTION",
annotationNames: ""
});
const templateAnnotationNames = (
task.template.template.annotations || []
).map(annotation => annotation.name);

return (
<>
<Card className="w-100">
<Card.Header>
<Nav className="justify-content-between">
<Nav.Item>
<span>Last Task Maintenance</span>
</Nav.Item>
<Nav.Item>
<Button variant="outline-success" onClick={handleClickNew}>
New Task Maintenance...
</Button>
</Nav.Item>
</Nav>
</Card.Header>
<ListGroup variant="flush">
{fetchJobs.map(job => (
<ListGroup.Item key={job.id}>
<Row className="align-items-center">
<Col>{job.id}</Col>
<Col>{JobTask.TYPE_TEXT[job.type]}</Col>
<Col>{JobTask.STATUS_TEXT[job.status]}</Col>
<Col>{JSON.stringify(job.config.result)}</Col>
<Col>
<div>
<small>Created: {parseDate(job.createdAt)}</small>
</div>
<div>
<small>
Started: {job.startedAt ? parseDate(job.startedAt) : "--"}
</small>
</div>
<div>
<small>
Finished:{" "}
{job.finishedAt ? parseDate(job.finishedAt) : "--"}
</small>
</div>
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
</Card>
<Modal show={showNewMaintenanceModal} onHide={handleCloseModal}>
<Modal.Header closeButton>
<Modal.Title>Task Maintenance</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form noValidate>
<Form.Group>
<Form.Label>Annotation</Form.Label>
<Form.Control
as="select"
value={values.annotations}
name="annotations"
onChange={handleChange}
isInvalid={touched.annotations && errors.annotations}
>
<option value="NO_ACTION">完了を外さない</option>
<option value="UNDO_ALL">
全てのアノテーションの完了を外す
</option>
<option value="UNDO_BY_NAME">
特定の種類のアノテーションの完了を外す
</option>
</Form.Control>
<Form.Control.Feedback type="invalid">
Annotationは必須です
</Form.Control.Feedback>
</Form.Group>
{values.annotations === "UNDO_BY_NAME" ? (
<Form.Group>
<Form.Label>Annotation Name List</Form.Label>
<Form.Control
as="select"
ref={annotationNamesSelect}
multiple
value={values.annotationNames.split(",")}
name="annotationNames"
onChange={evt => {
handleChange({
target: {
name: evt.target.name,
value: [...evt.target.selectedOptions]
.map(option => option.value)
.join(",")
}
});
}}
isInvalid={touched.annotationNames && errors.annotationNames}
>
{templateAnnotationNames.map(name => (
<option key={name} value={name}>
{name}
</option>
))}
</Form.Control>
<Form.Control.Feedback type="invalid">
Annotationは必須です
</Form.Control.Feedback>
</Form.Group>
) : null}
<Form.Group>
<Form.Label>Task Item</Form.Label>
<Form.Control
as="select"
value={values.taskItems}
name="taskItems"
onChange={handleChange}
isInvalid={touched.taskItems && errors.taskItems}
>
<option value="UNDO_ONGOING">
上で外したアノテーションがあるのみ完了を外す
</option>
<option value="UNDO_ALL">タスクの全件の完了を外す</option>
</Form.Control>
<Form.Control.Feedback type="invalid">
Task Itemは必須です
</Form.Control.Feedback>
</Form.Group>
<Form.Group>
<Form.Label>Task Assignment</Form.Label>
<Form.Control
as="select"
value={values.taskAssignments}
name="taskAssignments"
onChange={handleChange}
isInvalid={touched.taskAssignments && errors.taskAssignments}
>
<option value="NO_ACTION">完了を外さない</option>
<option value="UNDO_ONGOING">
上で外した画像・動画があるのみ完了を外す
</option>
<option value="UNDO_ALL">
アサインタスクの全件の完了を外す
</option>
</Form.Control>
<Form.Control.Feedback type="invalid">
Task Assignmentは必須です
</Form.Control.Feedback>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleCloseModal}>
Close
</Button>
<Button variant="success" onClick={handleSubmit}>
Perform Task Maintenance
</Button>
</Modal.Footer>
</Modal>
</>
);
}

export default AdminProjectTaskMaintenance;
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const AnnotatedVideo = function({
</video>
</div>
<AnnotatedVideoSeekbar
key={videoUri}
duration={duration}
currentTime={currentTime}
playing={playing}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ const AnnotatedVideoSeekbar = function({
};

const handleSkip = function(frames) {
console.log(frames);
setPlayUntil(+Infinity);
skipFrames(frames);
};
Expand Down
14 changes: 14 additions & 0 deletions packages/nota-client/src/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,20 @@ export async function refreshTaskItems({ projectId, taskId }) {
);
}

export async function performTaskMaintenance({
projectId,
taskId,
maintenance
}) {
return await fetch(`/api/projects/${projectId}/tasks/${taskId}/maintenance`, {
method: "post",
body: JSON.stringify(maintenance),
headers: new Headers({
"content-type": "application/json"
})
});
}

export async function deleteTask({ projectId, taskId }) {
return await fetch(`/api/projects/${projectId}/tasks/${taskId}`, {
method: "delete",
Expand Down
4 changes: 4 additions & 0 deletions packages/nota-server/src/bin/worker
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ notaJobQueues[JobTask.TASK_NAME.TASK_EXPORT].process(
CONCURRENCY,
`${__dirname}/../services/taskExport.js`
);
notaJobQueues[JobTask.TASK_NAME.TASK_MAINTENANCE].process(
CONCURRENCY,
`${__dirname}/../services/taskMaintenance.js`
);

const REPEAT_EVERY = 1000 * 60; // 1 minute

Expand Down
4 changes: 4 additions & 0 deletions packages/nota-server/src/lib/notaJobQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ const queues = {
[JobTask.TASK_NAME.TASK_EXPORT]: new Bull(
"job-queue-task-export",
queueOptions
),
[JobTask.TASK_NAME.TASK_MAINTENANCE]: new Bull(
"job-queue-task-maintenance",
queueOptions
)
};
logger.info({ logType: "service", message: "nota-job-service initialized" });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = function(task, processor) {
});
done();
} catch (error) {
logger.error(error);
jobTask.status = JobTask.STATUS.ERROR;
jobTask.finishedAt = new Date();
jobTask.config = { ...jobTask.config, error: error.message };
Expand Down
6 changes: 4 additions & 2 deletions packages/nota-server/src/models/jobTask.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ module.exports = function(sequelize) {
JobTask.TASK = {
MEDIA_SOURCE_FETCH: 1,
TASK_FETCH: 2,
TASK_EXPORT: 3
TASK_EXPORT: 3,
TASK_MAINTENANCE: 4
};
JobTask.TASK_NAME = {
MEDIA_SOURCE_FETCH: "MEDIA_SOURCE_FETCH",
TASK_FETCH: "TASK_FETCH",
TASK_EXPORT: "TASK_EXPORT"
TASK_EXPORT: "TASK_EXPORT",
TASK_MAINTENANCE: "TASK_MAINTENANCE"
};

JobTask.TYPE = {
Expand Down
Loading

0 comments on commit ba1d12e

Please sign in to comment.