Skip to content

Task creation can use task id #4404

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jan 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).

### Added
- Added new viewing permission for annotations: public (everyone with the link has access, logged in or not), internal (everyone from your organization has access), private (only you and your team managers and admins have access). The new default is internal as it is the old default non-public.
- Added support for using task ids as base for a new task, if the corresponding task only has one (finished) instance. [#4404](https://github.com/scalableminds/webknossos/pull/4404)

### Changed
- Changed the error message when importing a dataset without resolution directories. [#4389](https://github.com/scalableminds/webknossos/pull/4389)
Expand Down
33 changes: 30 additions & 3 deletions app/controllers/TaskController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.scalableminds.webknossos.tracingstore.tracings.{ProtoGeometryImplicit
import javax.inject.Inject
import models.annotation.nml.NmlResults.NmlParseResult
import models.annotation.nml.NmlService
import models.annotation.{Annotation, AnnotationDAO, AnnotationService, TracingStoreRpcClient, TracingStoreService}
import models.annotation._
import models.binary.{DataSetDAO, DataSetService}
import models.project.ProjectDAO
import models.task._
Expand Down Expand Up @@ -154,13 +154,39 @@ class TaskController @Inject()(annotationDAO: AnnotationDAO,

def duplicateBaseTracings(baseAnnotation: BaseAnnotation, taskParameters: TaskParameters, organizationId: ObjectId)(
implicit ctx: DBAccessContext,
m: MessagesProvider) =
m: MessagesProvider) = {

@SuppressWarnings(Array("TraversableHead")) // We check if nonCancelledTaskAnnotations are empty before so head always works
def checkForTask(taskId: ObjectId): Fox[Annotation] =
(for {
task <- taskDAO.findOne(taskId)
annotations <- annotationDAO.findAllByTaskIdAndType(taskId, AnnotationType.Task)
} yield {
val nonCancelledTaskAnnotations = annotations.filter(_.state != AnnotationState.Cancelled)
if (task.totalInstances == 1 && task.openInstances == 0 &&
nonCancelledTaskAnnotations.nonEmpty &&
nonCancelledTaskAnnotations.head.state == AnnotationState.Finished)
Fox.successful(nonCancelledTaskAnnotations.head)
else Fox.failure("task.notOneAnnotation")
}).flatten

def useAnnotationIdOrCheckForTask(annotationOrTaskId: ObjectId): Fox[Annotation] =
annotationDAO
.findOne(annotationOrTaskId)
.futureBox
.map {
case Full(value) => Fox.successful(value)
case _ => checkForTask(annotationOrTaskId)
}
.toFox
.flatten

for {
taskTypeIdValidated <- ObjectId.parse(taskParameters.taskTypeId) ?~> "taskType.id.invalid"
taskType <- taskTypeDAO.findOne(taskTypeIdValidated) ?~> "taskType.notFound"
dataSet <- dataSetDAO.findOneByNameAndOrganization(taskParameters.dataSet, organizationId)
baseAnnotationIdValidated <- ObjectId.parse(baseAnnotation.baseId)
annotation <- annotationDAO.findOne(baseAnnotationIdValidated)
annotation <- useAnnotationIdOrCheckForTask(baseAnnotationIdValidated)
tracingStoreClient <- tracingStoreService.clientFor(dataSet)
newSkeletonId <- if (taskType.tracingType == TracingType.skeleton || taskType.tracingType == TracingType.hybrid)
duplicateSkeletonTracingOrCreateSkeletonTracingBase(annotation, taskParameters, tracingStoreClient).map(Some(_))
Expand All @@ -170,6 +196,7 @@ class TaskController @Inject()(annotationDAO: AnnotationDAO,
.map(Some(_))
else Fox.successful(None)
} yield BaseAnnotation(baseAnnotationIdValidated.id, newSkeletonId, newVolumeId)
}

def createTaskSkeletonTracingBases(paramsList: List[TaskParameters])(
implicit ctx: DBAccessContext,
Expand Down
2 changes: 1 addition & 1 deletion conf/messages
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ task.noAnnotations=We couldn’t find finished annotations for this task
task.annotation.failed=Failed to retrieve annotations for this task
task.id.invalid=The provided task id is invalid.
task.notOnSameDataSet=Cannot create tasks on multiple datasets in one go.

task.notOneAnnotation=The specified base task does not have exactly one (finished) instance.
assignment.retrieval.failed=Failed to retrieve any assignment

taskType=Task type
Expand Down
4 changes: 2 additions & 2 deletions frontend/javascripts/admin/admin_rest_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,8 @@ export function createTaskFromNML(task: NewTask): Promise<Array<TaskCreationResp
});
}

export async function getTask(taskId: string): Promise<APITask> {
const task = await Request.receiveJSON(`/api/tasks/${taskId}`);
export async function getTask(taskId: string, options?: RequestOptions = {}): Promise<APITask> {
const task = await Request.receiveJSON(`/api/tasks/${taskId}`, options);
return transformTask(task);
}

Expand Down
33 changes: 26 additions & 7 deletions frontend/javascripts/admin/task/task_create_form_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,15 @@ class TaskCreateFormView extends React.PureComponent<Props, State> {
return (
<div>
{this.state.specificationType === SpecificationEnum.BaseAnnotation ? (
<FormItem label="Base Annotation ID" hasFeedback>
<FormItem label="Base ID" hasFeedback>
{getFieldDecorator("baseAnnotation.baseId", {
rules: [
{ required: true },
{
validator: async (rule, value, callback) => {
const response =
if (value === "") return callback();

const annotationResponse =
(await tryToAwaitPromise(
getAnnotationInformation(value, "Task", {
showErrorToast: false,
Expand All @@ -274,13 +276,30 @@ class TaskCreateFormView extends React.PureComponent<Props, State> {
}),
));

if (response != null && response.dataSetName != null) {
this.props.form.setFieldsValue({ dataSet: response.dataSetName });
if (annotationResponse != null && annotationResponse.dataSetName != null) {
this.props.form.setFieldsValue({
dataSet: annotationResponse.dataSetName,
});
return callback();
} else {
this.props.form.setFieldsValue({ dataSet: null });
return callback("Invalid base annotation id.");
}

const taskResponse = await tryToAwaitPromise(
getTask(value, { showErrorToast: false }),
);

if (
taskResponse != null &&
taskResponse.dataSet != null &&
_.isEqual(taskResponse.status, { open: 0, active: 0, finished: 1 })
) {
this.props.form.setFieldsValue({
dataSet: taskResponse.dataSet,
});
return callback();
}

this.props.form.setFieldsValue({ dataSet: null });
return callback("Invalid base annotation id.");
},
},
],
Expand Down