Skip to content

Commit

Permalink
Hook up async tasks to new AnnotationProject.status field (#5350)
Browse files Browse the repository at this point in the history
Hook up async tasks to new AnnotationProject.status field
update changelog
add .env.local to gitignore

Co-authored-by: James Santucci <james.santucci@gmail.com>
  • Loading branch information
Lknechtli and jisantuc authored Mar 11, 2020
1 parent 1cf8a8a commit ad61184
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 27 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ metals.sbt

/.env
/.envrc
.env.local

.node_modules
dist/
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
- Updated to support STAC exports on annotation projects [#5312](https://github.com/raster-foundry/raster-foundry/pull/5312) [#5323](https://github.com/raster-foundry/raster-foundry/pull/5323)
- Made permission replacement obey same scope rules [#5343](https://github.com/raster-foundry/raster-foundry/pull/5343)
- Updated the task grid creation SQL function to clip task cells to project footprint [#5344](https://github.com/raster-foundry/raster-foundry/pull/5344)
- Change the `ready` boolean field to a `status` enum field for better descriptions of processing failures [#5350](https://github.com/raster-foundry/raster-foundry/pull/5350)

### Fixed
- Upgrade pyproj to make app-tasks python3.7 compatible [#5352](https://github.com/raster-foundry/raster-foundry/pull/5352)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ class CreateTaskGrid(
.update(
annotationProject.copy(
aoi = footprint,
ready = true,
taskSizeMeters = Some(taskSizeMeters)
),
annotationProject.id
Expand Down
12 changes: 11 additions & 1 deletion app-backend/common/src/test/scala/com/implicits/Generators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,16 @@ object Generators extends ArbitraryInstances {
IngestStatus.Failed
)

private def annotationProjectStatusGen: Gen[AnnotationProjectStatus] = Gen.oneOf(
AnnotationProjectStatus.Waiting,
AnnotationProjectStatus.Queued,
AnnotationProjectStatus.Processing,
AnnotationProjectStatus.Ready,
AnnotationProjectStatus.UnknownFailure,
AnnotationProjectStatus.TaskGridFailure,
AnnotationProjectStatus.ImageIngestionFailure
)

private def tileLayerTypeGen: Gen[TileLayerType] = Gen.oneOf(
TileLayerType.MVT,
TileLayerType.TMS
Expand Down Expand Up @@ -1049,7 +1059,7 @@ object Generators extends ArbitraryInstances {
Gen.const(None),
tileLayerCreateGen map { List(_) },
Gen.listOfN(3, labelClassGroupGen),
arbitrary[Boolean]
annotationProjectStatusGen
).mapN(AnnotationProject.Create.apply _)

private def annotationLabelWithClassesCreateGen
Expand Down
14 changes: 7 additions & 7 deletions app-backend/datamodel/src/main/scala/AnnotationProject.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final case class AnnotationProject(
labelersTeamId: Option[UUID],
validatorsTeamId: Option[UUID],
projectId: Option[UUID],
ready: Boolean
status: AnnotationProjectStatus
) {
def withRelated(
tileLayers: List[TileLayer],
Expand All @@ -37,7 +37,7 @@ final case class AnnotationProject(
labelersTeamId,
validatorsTeamId,
projectId,
ready,
status,
tileLayers,
labelClassGroups
)
Expand All @@ -57,7 +57,7 @@ object AnnotationProject {
projectId: Option[UUID],
tileLayers: List[TileLayer.Create],
labelClassGroups: List[AnnotationLabelClassGroup.Create],
ready: Boolean
status: AnnotationProjectStatus
)

object Create {
Expand All @@ -76,7 +76,7 @@ object AnnotationProject {
labelersTeamId: Option[UUID],
validatorsTeamId: Option[UUID],
projectId: Option[UUID],
ready: Boolean,
status: AnnotationProjectStatus,
tileLayers: List[TileLayer],
labelClassGroups: List[AnnotationLabelClassGroup.WithLabelClasses]
) {
Expand All @@ -92,7 +92,7 @@ object AnnotationProject {
labelersTeamId,
validatorsTeamId,
projectId,
ready
status
)

def withSummary(
Expand All @@ -111,7 +111,7 @@ object AnnotationProject {
labelersTeamId,
validatorsTeamId,
projectId,
ready,
status,
tileLayers,
labelClassGroups,
taskStatusSummary,
Expand Down Expand Up @@ -158,7 +158,7 @@ object AnnotationProject {
labelersTeamId: Option[UUID],
validatorsTeamId: Option[UUID],
projectId: Option[UUID],
ready: Boolean,
status: AnnotationProjectStatus,
tileLayers: List[TileLayer],
labelClassGroups: List[AnnotationLabelClassGroup.WithLabelClasses],
taskStatusSummary: Map[TaskStatus, Int],
Expand Down
85 changes: 85 additions & 0 deletions app-backend/datamodel/src/main/scala/AnnotationProjectStatus.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.rasterfoundry.datamodel

import cats.implicits._
import io.circe._
import io.circe.syntax._

import scala.util.Try

abstract class AnnotationProjectStatus(val repr: String)

abstract class ProgressStage(val stage: String)
extends AnnotationProjectStatus(stage)
abstract class ErrorStage(val stage: String)
extends AnnotationProjectStatus(stage)

object ErrorStage {
val fromString = new PartialFunction[String, ErrorStage] {
def apply(s: String) = s.toUpperCase match {
case "TASK_GRID_FAILURE" => AnnotationProjectStatus.TaskGridFailure
case "IMAGE_INGESTION_FAILURE" =>
AnnotationProjectStatus.ImageIngestionFailure
case "UNKNOWN_FAILURE" => AnnotationProjectStatus.UnknownFailure
}
def isDefinedAt(s: String): Boolean =
Set("TASK_GRID_FAILURE", "IMAGE_INGESTION_FAILURE", "UNKNOWN_FAILURE")
.contains(s.toUpperCase)
}

}

object ProgressStage {
val fromString = new PartialFunction[String, ProgressStage] {
def apply(s: String) = s.toUpperCase match {
case "WAITING" => AnnotationProjectStatus.Waiting
case "QUEUED" => AnnotationProjectStatus.Queued
case "PROCESSING" => AnnotationProjectStatus.Processing
case "READY" => AnnotationProjectStatus.Ready
}
def isDefinedAt(s: String): Boolean =
Set("WAITING", "QUEUED", "PROCESSING", "READY").contains(s.toUpperCase)
}
}

object AnnotationProjectStatus {
case object Waiting extends ProgressStage("WAITING")
case object Queued extends ProgressStage("QUEUED")
case object Processing extends ProgressStage("PROCESSING")
case object Ready extends ProgressStage("READY")

case object TaskGridFailure extends ErrorStage("TASK_GRID_FAILURE")
case object ImageIngestionFailure
extends ErrorStage("IMAGE_INGESTION_FAILURE")
case object UnknownFailure extends ErrorStage("UNKNOWN_FAILURE")

implicit val decoderErrorStatus: Decoder[ErrorStage] =
Decoder.forProduct1("errorStage")(
(stage: String) => ErrorStage.fromString(stage)
)
implicit val encoderErrorStatus: Encoder[ErrorStage] =
Encoder.forProduct1("errorStage")(
(stage: ErrorStage) => stage.repr
)
implicit val decoderProgressStatus: Decoder[ProgressStage] =
Decoder.decodeString.emapTry(
(status: String) => Try { ProgressStage.fromString(status) }
)
implicit val encoderProgressStatus: Encoder[ProgressStage] =
Encoder.forProduct1("status")(
(status: ProgressStage) => status.repr
)

def fromString: PartialFunction[String, AnnotationProjectStatus] =
ErrorStage.fromString.orElse(ProgressStage.fromString)

implicit def encAnnProjStat: Encoder[AnnotationProjectStatus] =
new Encoder[AnnotationProjectStatus] {
def apply(thing: AnnotationProjectStatus): Json = thing match {
case ps: ProgressStage => Map("status" -> ps.repr).asJson
case es: ErrorStage => Map("errorStage" -> es.repr).asJson
}
}

implicit def decAnnProjStat: Decoder[AnnotationProjectStatus] =
Decoder[ProgressStage].widen or Decoder[ErrorStage].widen
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
CREATE TYPE public.annotation_project_status AS ENUM (
'WAITING',
'QUEUED',
'PROCESSING',
'READY',
'UNKNOWN_FAILURE',
'TASK_GRID_FAILURE',
'IMAGE_INGESTION_FAILURE'
);

ALTER TABLE public.annotation_projects
ADD COLUMN status public.annotation_project_status;

-- set default values and add null constraint
ALTER TABLE public.annotation_projects ADD CONSTRAINT annotation_project_status_not_null CHECK (status IS NOT NULL) NOT VALID;

UPDATE public.annotation_projects SET status = 'READY' WHERE ready = true;
UPDATE public.annotation_projects SET status = 'UNKNOWN_FAILURE' WHERE ready = false;

ALTER TABLE public.annotation_projects VALIDATE CONSTRAINT annotation_project_status_not_null;

-- drop old column
ALTER TABLE annotation_projects DROP COLUMN ready;
10 changes: 5 additions & 5 deletions app-backend/db/src/main/scala/AnnotationProjectDao.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ object AnnotationProjectDao
SELECT
id, created_at, owner, name, project_type, task_size_meters,
task_size_pixels, aoi, labelers_team_id, validators_team_id,
project_id, ready
project_id, status
FROM
""" ++ tableF

Expand Down Expand Up @@ -129,13 +129,13 @@ object AnnotationProjectDao
): ConnectionIO[AnnotationProject.WithRelated] = {
val projectInsert = (fr"INSERT INTO" ++ tableF ++ fr"""
(id, created_at, owner, name, project_type, task_size_pixels,
aoi, labelers_team_id, validators_team_id, project_id, ready)
aoi, labelers_team_id, validators_team_id, project_id, status)
VALUES
(uuid_generate_v4(), now(), ${user.id}, ${newAnnotationProject.name},
${newAnnotationProject.projectType}, ${newAnnotationProject.taskSizePixels},
${newAnnotationProject.aoi}, ${newAnnotationProject.labelersTeamId},
${newAnnotationProject.validatorsTeamId},
${newAnnotationProject.projectId}, ${newAnnotationProject.ready})
${newAnnotationProject.projectId}, ${newAnnotationProject.status})
""").update.withUniqueGeneratedKeys[AnnotationProject](
"id",
"created_at",
Expand All @@ -148,7 +148,7 @@ object AnnotationProjectDao
"labelers_team_id",
"validators_team_id",
"project_id",
"ready"
"status"
)

for {
Expand Down Expand Up @@ -230,7 +230,7 @@ object AnnotationProjectDao
validators_team_id = ${project.validatorsTeamId},
task_size_meters= ${project.taskSizeMeters},
aoi = ${project.aoi},
ready = ${project.ready}
status = ${project.status}
WHERE
id = $id
""").update.run;
Expand Down
7 changes: 7 additions & 0 deletions app-backend/db/src/main/scala/meta/EnumMeta.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ trait EnumMeta {
_.repr
)

implicit val annotationProjectStatusMeta: Meta[AnnotationProjectStatus] =
pgEnumString(
"annotation_project_status",
AnnotationProjectStatus.fromString,
_.repr
)

implicit val tileLayerTypeMeta: Meta[TileLayerType] =
pgEnumString(
"tile_layer_type",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ class AnnotationProjectDaoSpec
)

assert(
afterUpdate.ready == annotationProjectUpdate.ready,
afterUpdate.status == annotationProjectUpdate.status,
"Readiness was updated")

true
Expand Down
56 changes: 47 additions & 9 deletions app-tasks/rf/src/rf/commands/process_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import click
from planet import api

from ..models import Upload
from ..models import Upload, AnnotationProject
from ..uploads.geotiff import GeoTiffS3SceneFactory
from ..uploads.geotiff.io import update_annotation_project
from ..uploads.landsat_historical import LandsatHistoricalSceneFactory
Expand All @@ -18,6 +18,10 @@
JOB_ATTEMPT = int(os.getenv("AWS_BATCH_JOB_ATTEMPT", -1))


class TaskGridError(Exception):
pass


@click.command(name="process-upload")
@click.argument("upload_id")
@wrap_rollbar
Expand All @@ -33,6 +37,12 @@ def process_upload(upload_id):
upload = Upload.from_id(upload_id)
logger.info("Updating upload status")
upload.update_upload_status("Processing")
annotationProject = None
if upload.annotationProjectId is not None:
logger.info("Getting annotation project: %s", upload.annotationProjectId)
annotationProject = AnnotationProject.from_id(upload.annotationProjectId)
logger.info("Updating annotation project status")
annotationProject.update_status("Processing")

logger.info(
"Processing upload (%s) for user %s with files %s",
Expand Down Expand Up @@ -124,20 +134,48 @@ def process_upload(upload_id):

generate_tasks = upload.annotationProjectId is not None and upload.generateTasks
if generate_tasks:
[
update_annotation_project(
upload.annotationProjectId, scene.ingestLocation.replace("%7C", "|"))
for scene in created_scenes
]
except:
try:
[
update_annotation_project(
upload.annotationProjectId, scene.ingestLocation.replace("%7C", "|"))
for scene in created_scenes
]
except Exception as e:
raise TaskGridError("Error making task grid: %s", e)
if annotationProject is not None:
# Don't overwrite fields modified by the task grid creation
annotationProject = AnnotationProject.from_id(upload.annotationProjectId)
annotationProject.update_status("READY")
except TaskGridError as tge:
logger.error(
"Failed to process upload (%s) for user %s with files %s",
"Error making task grids annotation project (%s) on upload (%s) for with files %s. %s",
annotationProject.id,
upload.id,
upload.owner,
upload.files,
tge
)
if JOB_ATTEMPT >= 3:
upload.update_upload_status("FAILED")
annotationProject.update_status({"errorStage": "TASK_GRID_FAILURE"})
else:
upload.update_upload_status("QUEUED")
if annotationProject is not None:
annotationProject.update_status("QUEUED")
raise
except:
if annotationProject is not None:
logger.error("Upload for AnnotationProject failed to process: %s", annotationProject.id)
if JOB_ATTEMPT >= 3:
upload.update_upload_status("FAILED")
annotationProject.update_status({"errorStage": "IMAGE_INGESTION_FAILURE"})
else:
upload.update_upload_status("QUEUED")
if annotationProject is not None:
annotationProject.update_status("QUEUED")
logger.error(
"Failed to process upload (%s) for user %s with files %s",
upload.id,
upload.owner,
upload.files,
)
raise
Loading

0 comments on commit ad61184

Please sign in to comment.