-
Notifications
You must be signed in to change notification settings - Fork 4
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
[GAWB-2056] The status of the last run is showing green when there are errors in them #709
Changes from 8 commits
cbe29ff
21b62b0
fe4fcfa
3ee7afb
5144a52
7136d42
22d8f0b
ee31eb5
a5120c7
746feaa
f819423
2a7610e
7927a22
103a4da
bc1aebf
3553256
bb205b3
00df98b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,16 @@ package org.broadinstitute.dsde.rawls.dataaccess.slick | |
import java.sql.Timestamp | ||
import java.util.{Date, UUID} | ||
|
||
import cats.{Monoid, MonoidK} | ||
import cats.instances.int._ | ||
import cats.instances.option._ | ||
import org.broadinstitute.dsde.rawls.RawlsException | ||
import org.broadinstitute.dsde.rawls.dataaccess.SlickWorkspaceContext | ||
import org.broadinstitute.dsde.rawls.model.Attributable.AttributeMap | ||
import org.broadinstitute.dsde.rawls.model.WorkspaceAccessLevels.WorkspaceAccessLevel | ||
import org.broadinstitute.dsde.rawls.model._ | ||
import org.broadinstitute.dsde.rawls.util.CollectionUtils | ||
import org.joda.time.DateTime | ||
import org.broadinstitute.dsde.rawls.dataaccess.SlickWorkspaceContext | ||
import org.broadinstitute.dsde.rawls.model.Attributable.AttributeMap | ||
|
||
/** | ||
* Created by dvoet on 2/4/16. | ||
*/ | ||
|
@@ -499,36 +502,56 @@ trait WorkspaceComponent { | |
} | ||
|
||
/** | ||
* gets the submission stats (last workflow failed date, last workflow success date, running submission count) | ||
* gets the submission stats (last submission failed date, last submission success date, running submission count) | ||
* for each workspace | ||
* | ||
* @param workspaceIds the workspace ids to query for | ||
* @return WorkspaceSubmissionStats keyed by workspace id | ||
*/ | ||
def listSubmissionSummaryStats(workspaceIds: Seq[UUID]): ReadAction[Map[UUID, WorkspaceSubmissionStats]] = { | ||
// workflow date query: select workspaceId, workflow.status, max(workflow.statusLastChangedDate) ... group by workspaceId, workflow.status | ||
val workflowDatesQuery = for { | ||
|
||
// submission date query: | ||
// | ||
// select workspaceId, status, max(submissionDate) | ||
// from ( | ||
// select distinct submission.workspaceId, workflow.status, submission.submissionDate | ||
// from submission | ||
// join workflow on workflow.submissionId = submission.id | ||
// where submission.workspaceId in (:workspaceIds)) v | ||
// group by 1, 2 | ||
// having (status = 'Failure' or (status = 'Succeeded' and count(v.*) = 1)) | ||
|
||
val workflowStatusQuery = (for { | ||
submissions <- submissionQuery if submissions.workspaceId.inSetBind(workspaceIds) | ||
workflows <- workflowQuery if submissions.id === workflows.submissionId | ||
} yield (submissions.workspaceId, workflows.status, workflows.statusLastChangedDate) | ||
} yield (submissions.workspaceId, workflows.status, submissions.submissionDate)).distinct | ||
|
||
val submissionMaxDateQuery = workflowStatusQuery.groupBy { case (workspaceId, status, submissionDate) => | ||
(workspaceId, status) | ||
}.map { case ((workspaceId, status), recs) => | ||
(workspaceId, status, recs.map(_._3).max, recs.length) | ||
} | ||
|
||
val workflowDatesGroupedQuery = workflowDatesQuery.groupBy { case (wsId, status, _) => (wsId, status) }. | ||
map { case ((wsId, wfStatus), records) => (wsId, wfStatus, records.map { case (_, _, lastChanged) => lastChanged }.max) } | ||
// Note: a submission is successful if it contains _only_ successful workflows. | ||
// A submission is a failure if it contains _any_ failed workflows. | ||
val filteredSubmissionMaxDateQuery = submissionMaxDateQuery.filter { case (_, status, _, count) => | ||
status === WorkflowStatuses.Failed.toString || (status === WorkflowStatuses.Succeeded.toString && count === 1) | ||
}.map { case (workspaceId, status, max, _) => (workspaceId, status, max)} | ||
|
||
// running submission query: select workspaceId, count(1) ... where submissions.status === Submitted group by workspaceId | ||
val runningSubmissionsQuery = (for { | ||
submissions <- submissionQuery if submissions.workspaceId.inSetBind(workspaceIds) && submissions.status.inSetBind(SubmissionStatuses.activeStatuses.map(_.toString)) | ||
} yield submissions).groupBy(_.workspaceId).map { case (wfId, submissions) => (wfId, submissions.length)} | ||
|
||
for { | ||
workflowDates <- workflowDatesGroupedQuery.result | ||
submissionDates <- filteredSubmissionMaxDateQuery.result | ||
runningSubmissions <- runningSubmissionsQuery.result | ||
} yield { | ||
val workflowDatesByWorkspaceByStatus: Map[UUID, Map[String, Option[Timestamp]]] = groupByWorkspaceIdThenStatus(workflowDates) | ||
val submissionDatesByWorkspaceByStatus: Map[UUID, Map[String, Option[Timestamp]]] = groupByWorkspaceIdThenStatus(submissionDates) | ||
val runningSubmissionCountByWorkspace: Map[UUID, Int] = groupByWorkspaceId(runningSubmissions) | ||
|
||
workspaceIds.map { wsId => | ||
val (lastFailedDate, lastSuccessDate) = workflowDatesByWorkspaceByStatus.get(wsId) match { | ||
val (lastFailedDate, lastSuccessDate) = submissionDatesByWorkspaceByStatus.get(wsId) match { | ||
case None => (None, None) | ||
case Some(datesByStatus) => | ||
(datesByStatus.getOrElse(WorkflowStatuses.Failed.toString, None), datesByStatus.getOrElse(WorkflowStatuses.Succeeded.toString, None)) | ||
|
@@ -733,11 +756,22 @@ trait WorkspaceComponent { | |
} | ||
|
||
private def groupByWorkspaceId(runningSubmissions: Seq[(UUID, Int)]): Map[UUID, Int] = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These methods seemed useful so I cats-ified and generalized them and moved to |
||
runningSubmissions.groupBy{ case (wsId, count) => wsId }.mapValues { case Seq((_, count)) => count } | ||
CollectionUtils.groupPairs(runningSubmissions.toList) | ||
} | ||
|
||
private def groupByWorkspaceIdThenStatus(workflowDates: Seq[(UUID, String, Option[Timestamp])]): Map[UUID, Map[String, Option[Timestamp]]] = { | ||
workflowDates.groupBy { case (wsId, _, _) => wsId }.mapValues(_.groupBy { case (_, status, _) => status }.mapValues { case Seq((_, _, timestamp)) => timestamp }) | ||
// There is no Monoid instance for Option[Timestamp] so we need to bring one into scope. | ||
// However a Monoid for Timestamp doesn't really make sense -- what would it do, add them together? | ||
// We can take advantage of the _universal_ monoid for Option which combines Option values using | ||
// Option.orElse. It's called universal because it works no matter the type inside the Option. | ||
// This is fine in this case because there are guaranteed no key conflicts due to the SQL query | ||
// structure (group by, etc). | ||
// | ||
// TL/DR: The following line brings into scope a Monoid[Option[Timestamp]] which combines values | ||
// using Option.orElse. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think part of my confusion here is that you're introducing Monoid, which is used to combine things, using a non-combining operation ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At least the
it's still expecting a unique UUID/String combo, and if that were not the case, there would be a runtime There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still think this is less readable than doing it explicitly but I'm going on vacation tomorrow and thus can't afford to spend more time arguing about it :) Instead, if you replace your comment with the following one, I'll shut up and thumb this. In the long-term, we need a centralised place for these explanations, because throwing them in comments on their first use won't help new hires understand them if they happen upon the uncommented second or third use first. But we can worry about that (a little) later. The function groupTriples, called below, transforms a Seq((T1, T2, T3)) to a Map(T1 -> Map(T2 -> T3)). It does this by calling foldMap, which in turn requires a monoid for T3. In our case, T3 is an Option[Timestamp], so we need to provide an implicit monoid for Option[Timestamp]. There isn't really a sane monoid implementation for Timestamp (what would you do, add them?). Thankfully it turns out that the UUID/String pairs in workflowDates are always unique, so it doesn't matter what the monoid does because it'll never be used to combine two Option[Timestamp]s. It just needs to be provided in order to make the compiler happy. To do this, we use the universal monoid for Option, MonoidK[Option]. Note that the inner Option takes no type parameter: MonoidK doesn't care about the type inside Option, it just calls orElse on the Option for its "combine" operator. Finally, the call to algebra[Timestamp] turns a MonoidK[Option] into a Monoid[Option[Timestamp]] by leaving the monoid implementation alone (so it still calls orElse) and poking the Timestamp type into the Option.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok :) |
||
|
||
implicit val optionTimestampMonoid: Monoid[Option[Timestamp]] = MonoidK[Option].algebra[Timestamp] | ||
CollectionUtils.groupTriples(workflowDates.toList) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you don't need the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks |
||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,10 @@ | ||
package org.broadinstitute.dsde.rawls.util | ||
|
||
import cats.Monoid | ||
import cats.instances.list._ | ||
import cats.instances.map._ | ||
import cats.syntax.foldable._ | ||
|
||
object CollectionUtils { | ||
|
||
//A saner group by than Scala's. | ||
|
@@ -10,4 +15,20 @@ object CollectionUtils { | |
def groupByTuplesFlatten[A, B]( tupleSeq: Seq[(A, Seq[B])] ): Map[A, Seq[B]] = { | ||
tupleSeq groupBy { case (a,b) => a } map { case (k, v) => k -> v.flatMap(_._2) } | ||
} | ||
|
||
/** | ||
* Converts a `Seq[(A, B)]` into a `Map[A, B]`, combining the values with a `Monoid[B]` in case of key conflicts. | ||
* | ||
* For example: | ||
* {{{ | ||
* scala> groupPairs(Seq(("a", 1), ("b", 2), ("a", 3))) | ||
* res0: Map[String,Int] = Map(b -> 2, a -> 4) | ||
* }}} | ||
* */ | ||
def groupPairs[A, B: Monoid](pairs: List[(A, B)]): Map[A, B] = | ||
pairs.foldMap { case (a, b) => Map(a -> b) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also these should probably prefer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re Seq: sure I guess this would work:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would it prefer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seq is less specific and we use it all over the place. We'd have to start jamming There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe people shouldn't have overused We have the same problem too and it's annoying. We occasionally run into issues where the thing really only works properly on List but things compile due to Seq and someone jammed something bad in there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rtitle Cuz the Cats folk understand the value in saying what you mean ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for your input, Jeff! When we wholesale switch from Seq to List I'll let you know so you can say I told you so. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll change these to take I won't touch groupByTuples/groupByTuplesFlatten because that would require introducing monoids and I don't want to break any code. |
||
|
||
// Same as above but with triples | ||
def groupTriples[A, B, C: Monoid](trips: List[(A, B, C)]): Map[A, Map[B, C]] = | ||
trips.foldMap { case (a, b, c) => Map(a -> Map(b -> c)) } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@helgridly based on our conversation how does this revised query look to you?
Explanation:
I can code this up and try to break it with tests too, just thought I'd post the SQL first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's right, though SQL isn't my native language!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually after some testing I think it's not right. SQL is not my native language either (or maybe it's my slick mapping). :( Still working through it, expect another iteration...