@@ -6,7 +6,7 @@ import com.scalableminds.util.tools.{Fox, JsonHelper}
66import com .scalableminds .webknossos .schema .Tables ._
77import models .job .JobState .JobState
88import models .job .JobCommand .JobCommand
9- import play .api .libs .json .{JsObject , Json }
9+ import play .api .libs .json .{JsObject , Json , OFormat }
1010import slick .jdbc .PostgresProfile .api ._
1111import slick .jdbc .TransactionIsolation .Serializable
1212import slick .lifted .Rep
@@ -22,19 +22,21 @@ case class Job(
2222 _owner : ObjectId ,
2323 _dataStore : String ,
2424 command : JobCommand ,
25- commandArgs : JsObject = Json .obj(),
25+ args : JsObject = Json .obj(),
2626 state : JobState = JobState .PENDING ,
2727 manualState : Option [JobState ] = None ,
2828 _worker : Option [ObjectId ] = None ,
2929 _voxelyticsWorkflowHash : Option [String ] = None ,
3030 latestRunId : Option [String ] = None ,
3131 returnValue : Option [String ] = None ,
3232 retriedBySuperUser : Boolean = false ,
33- started : Option [Long ] = None ,
34- ended : Option [Long ] = None ,
33+ started : Option [Instant ] = None ,
34+ ended : Option [Instant ] = None ,
3535 created : Instant = Instant .now,
3636 isDeleted : Boolean = false
37- ) {
37+ ) extends JobResultLinks {
38+ protected def id : ObjectId = _id
39+
3840 def isEnded : Boolean = {
3941 val relevantState = manualState.getOrElse(state)
4042 relevantState == JobState .SUCCESS || state == JobState .FAILURE
@@ -44,58 +46,12 @@ case class Job(
4446 for {
4547 e <- ended
4648 s <- started
47- } yield ( e - s).millis
49+ } yield e - s
4850
49- private def effectiveState : JobState = manualState.getOrElse(state)
51+ def effectiveState : JobState = manualState.getOrElse(state)
5052
5153 def exportFileName : Option [String ] = argAsStringOpt(" export_file_name" )
5254
53- def datasetName : Option [String ] = argAsStringOpt(" dataset_name" )
54-
55- def datasetId : Option [String ] = argAsStringOpt(" dataset_id" )
56-
57- private def argAsStringOpt (key : String ) = (commandArgs \ key).toOption.flatMap(_.asOpt[String ])
58- private def argAsBooleanOpt (key : String ) = (commandArgs \ key).toOption.flatMap(_.asOpt[Boolean ])
59-
60- def resultLink (organizationId : String ): Option [String ] =
61- if (effectiveState != JobState .SUCCESS ) None
62- else {
63- command match {
64- case JobCommand .convert_to_wkw | JobCommand .compute_mesh_file =>
65- datasetId.map { datasetId =>
66- val datasetNameMaybe = datasetName.map(name => s " $name- " ).getOrElse(" " )
67- Some (s " /datasets/ $datasetNameMaybe$datasetId/view " )
68- }.getOrElse(datasetName.map(name => s " datasets/ $organizationId/ $name/view " ))
69- case JobCommand .export_tiff | JobCommand .render_animation =>
70- Some (s " /api/jobs/ ${this ._id}/export " )
71- case JobCommand .infer_neurons if this .argAsBooleanOpt(" do_evaluation" ).getOrElse(false ) =>
72- returnValue.map { resultAnnotationLink =>
73- resultAnnotationLink
74- }
75- case JobCommand .infer_nuclei | JobCommand .infer_neurons | JobCommand .materialize_volume_annotation |
76- JobCommand .infer_with_model | JobCommand .infer_mitochondria | JobCommand .align_sections |
77- JobCommand .infer_instances =>
78- // There exist jobs with dataset name as return value, some with directoryName, and newest with datasetId
79- // Construct links that work in either case.
80- returnValue.map { datasetIdentifier =>
81- ObjectId
82- .fromStringSync(datasetIdentifier)
83- .map { asObjectId =>
84- s " /datasets/ $asObjectId/view "
85- }
86- .getOrElse(s " /datasets/ $organizationId/ $datasetIdentifier/view " )
87- }
88- case _ => None
89- }
90- }
91-
92- def resultLinkPublic (organizationId : String , webknossosPublicUrl : String ): Option [String ] =
93- for {
94- resultLink <- resultLink(organizationId)
95- resultLinkPublic = if (resultLink.startsWith(" /" )) s " $webknossosPublicUrl$resultLink"
96- else s " $resultLink"
97- } yield resultLinkPublic
98-
9955 def resultLinkSlackFormatted (organizationId : String , webknossosPublicUrl : String ): String =
10056 (for {
10157 resultLink <- resultLinkPublic(organizationId, webknossosPublicUrl)
@@ -108,6 +64,37 @@ case class Job(
10864 }.getOrElse(" " )
10965}
11066
67+ case class JobCompactInfo (
68+ id : ObjectId ,
69+ command : JobCommand ,
70+ organizationId : String ,
71+ ownerFirstName : String ,
72+ ownerLastName : String ,
73+ ownerEmail : String ,
74+ args : JsObject ,
75+ state : JobState ,
76+ returnValue : Option [String ],
77+ resultLink : Option [String ],
78+ voxelyticsWorkflowHash : Option [String ],
79+ created : Instant ,
80+ started : Option [Instant ],
81+ ended : Option [Instant ],
82+ creditCost : Option [scala.math.BigDecimal ]
83+ ) extends JobResultLinks {
84+
85+ protected def effectiveState : JobState = state
86+
87+ def enrich : JobCompactInfo = this .copy(
88+ resultLink = constructResultLink(organizationId),
89+ args = args - " webknossos_token" - " user_auth_token"
90+ )
91+
92+ }
93+
94+ object JobCompactInfo {
95+ implicit val jsonFormat : OFormat [JobCompactInfo ] = Json .format[JobCompactInfo ]
96+ }
97+
11198class JobDAO @ Inject ()(sqlClient : SqlClient )(implicit ec : ExecutionContext )
11299 extends SQLDAO [Job , JobsRow , Jobs ](sqlClient) {
113100 protected val collection = Jobs
@@ -135,8 +122,8 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext)
135122 r.latestrunid,
136123 r.returnvalue,
137124 r.retriedbysuperuser,
138- r.started.map(_.getTime ),
139- r.ended.map(_.getTime ),
125+ r.started.map(Instant .fromSql ),
126+ r.ended.map(Instant .fromSql ),
140127 Instant .fromSql(r.created),
141128 r.isdeleted
142129 )
@@ -159,16 +146,62 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext)
159146 )
160147 """
161148
162- private def listAccessQ (requestingUserId : ObjectId ) =
163- q """ _owner = $requestingUserId OR
164- ((SELECT u._organization FROM webknossos.users_ u WHERE u._id = _owner) IN (SELECT _organization FROM webknossos.users_ WHERE _id = $requestingUserId AND isAdmin))
165- """
149+ private def listAccessQ (requestingUserId : ObjectId , prefix : SqlToken ) =
150+ q """ ${prefix} _owner = $requestingUserId OR
151+ ((SELECT u._organization FROM webknossos.users_ u WHERE u._id = ${prefix} _owner) IN (SELECT _organization FROM webknossos.users_ WHERE _id = $requestingUserId AND isAdmin))
152+ """
166153
167- override def findAll (implicit ctx : DBAccessContext ): Fox [List [ Job ]] =
154+ def findAllCompact (implicit ctx : DBAccessContext ): Fox [Seq [ JobCompactInfo ]] =
168155 for {
169- accessQuery <- accessQueryFromAccessQ(listAccessQ)
170- r <- run(q " SELECT $columns FROM $existingCollectionName WHERE $accessQuery ORDER BY created " .as[JobsRow ])
171- parsed <- parseAll(r)
156+ accessQuery <- accessQueryFromAccessQWithPrefix(listAccessQ, q " j. " )
157+ rows <- run(
158+ q """
159+ SELECT j._id, j.command, u._organization, u.firstName, u.lastName, mu.email, j.commandArgs, COALESCE(j.manualState, j.state),
160+ j.returnValue, j._voxelytics_workflowHash, j.created, j.started, j.ended, ct.credit_delta
161+ FROM webknossos.jobs_ j
162+ JOIN webknossos.users_ u on j._owner = u._id
163+ JOIN webknossos.multiusers_ mu on u._multiUser = mu._id
164+ LEFT JOIN webknossos.credit_transactions_ ct ON j._id = ct._paid_job
165+ WHERE $accessQuery
166+ ORDER BY j.created DESC -- list newest first
167+ """ .as[(ObjectId ,
168+ String ,
169+ String ,
170+ String ,
171+ String ,
172+ String ,
173+ String ,
174+ String ,
175+ Option [String ],
176+ Option [String ],
177+ Instant ,
178+ Option [Instant ],
179+ Option [Instant ],
180+ Option [scala.math.BigDecimal ])])
181+ parsed <- Fox .serialCombined(rows) { row =>
182+ for {
183+ command <- JobCommand .fromString(row._2).toFox
184+ effectiveState <- JobState .fromString(row._8).toFox
185+ commandArgs <- JsonHelper .parseAs[JsObject ](row._7).toFox
186+ } yield
187+ JobCompactInfo (
188+ id = row._1,
189+ command = command,
190+ organizationId = row._3,
191+ ownerFirstName = row._4,
192+ ownerLastName = row._5,
193+ ownerEmail = row._6,
194+ args = commandArgs,
195+ state = effectiveState,
196+ returnValue = row._9,
197+ resultLink = None , // To be filled by calling “enrich”
198+ voxelyticsWorkflowHash = row._10,
199+ created = row._11,
200+ started = row._12,
201+ ended = row._13,
202+ creditCost = row._14.map(_ * - 1 ) // delta is negative, so cost should be positive.
203+ )
204+ }
172205 } yield parsed
173206
174207 override def findOne (jobId : ObjectId )(implicit ctx : DBAccessContext ): Fox [Job ] =
@@ -250,7 +283,7 @@ class JobDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext)
250283 created, isDeleted
251284 )
252285 VALUES(
253- ${j._id}, ${j._owner}, ${j._dataStore}, ${j.command}, ${j.commandArgs },
286+ ${j._id}, ${j._owner}, ${j._dataStore}, ${j.command}, ${j.args },
254287 ${j.state}, ${j.manualState}, ${j._worker},
255288 ${j.latestRunId}, ${j.returnValue}, ${j.started}, ${j.ended},
256289 ${j.created}, ${j.isDeleted}) """ .asUpdate)
0 commit comments