Skip to content

Commit

Permalink
#14 Add merge information in stats ; add nstats parameter in get case…
Browse files Browse the repository at this point in the history
… api call
  • Loading branch information
To-om committed Nov 18, 2016
1 parent d3b576e commit f63d737
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 50 deletions.
49 changes: 27 additions & 22 deletions thehive-backend/app/controllers/Case.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,46 +41,51 @@ class CaseCtrl @Inject() (
val log = Logger(getClass)

@Timed
def create() = authenticated(Role.write).async(fieldsBodyParser) { implicit request =>
def create() = authenticated(Role.write).async(fieldsBodyParser) { implicit request
caseSrv.create(request.body)
.map(caze => renderer.toOutput(CREATED, caze))
.map(caze renderer.toOutput(CREATED, caze))
}

@Timed
def get(id: String) = authenticated(Role.read).async { implicit request =>
caseSrv.get(id)
.map(caze => renderer.toOutput(OK, caze))
def get(id: String) = authenticated(Role.read).async(fieldsBodyParser) { implicit request
val nparent = request.body.getLong("nparent").getOrElse(0L).toInt
val withStats = request.body.getBoolean("nstats").getOrElse(false)

for {
caze caseSrv.get(id)
casesWithStats auxSrv.apply(caze, nparent, withStats)
} yield renderer.toOutput(OK, casesWithStats)
}

@Timed
def update(id: String) = authenticated(Role.write).async(fieldsBodyParser) { implicit request =>
def update(id: String) = authenticated(Role.write).async(fieldsBodyParser) { implicit request
val isCaseClosing = request.body.getString("status").filter(_ == CaseStatus.Resolved.toString).isDefined

for {
// Closing the case, so lets close the open tasks
caze <- caseSrv.update(id, request.body)
closedTasks <- if (isCaseClosing) taskSrv.closeTasksOfCase(id) else Future.successful(Nil) // FIXME log warning if closedTasks contains errors
caze caseSrv.update(id, request.body)
closedTasks if (isCaseClosing) taskSrv.closeTasksOfCase(id) else Future.successful(Nil) // FIXME log warning if closedTasks contains errors
} yield renderer.toOutput(OK, caze)
}

@Timed
def bulkUpdate() = authenticated(Role.write).async(fieldsBodyParser) { implicit request =>
def bulkUpdate() = authenticated(Role.write).async(fieldsBodyParser) { implicit request
val isCaseClosing = request.body.getString("status").filter(_ == CaseStatus.Resolved.toString).isDefined

request.body.getStrings("ids").fold(Future.successful(Ok(JsArray()))) { ids =>
request.body.getStrings("ids").fold(Future.successful(Ok(JsArray()))) { ids
if (isCaseClosing) taskSrv.closeTasksOfCase(ids: _*) // FIXME log warning if closedTasks contains errors
caseSrv.bulkUpdate(ids, request.body.unset("ids")).map(multiResult => renderer.toMultiOutput(OK, multiResult))
caseSrv.bulkUpdate(ids, request.body.unset("ids")).map(multiResult renderer.toMultiOutput(OK, multiResult))
}
}

@Timed
def delete(id: String) = authenticated(Role.write).async { implicit request =>
def delete(id: String) = authenticated(Role.write).async { implicit request
caseSrv.delete(id)
.map(_ => NoContent)
.map(_ NoContent)
}

@Timed
def find() = authenticated(Role.read).async(fieldsBodyParser) { implicit request =>
def find() = authenticated(Role.read).async(fieldsBodyParser) { implicit request
val query = request.body.getValue("query").fold[QueryDef](QueryDSL.any)(_.as[QueryDef])
val range = request.body.getString("range")
val sort = request.body.getStrings("sort").getOrElse(Nil)
Expand All @@ -93,21 +98,21 @@ class CaseCtrl @Inject() (
}

@Timed
def stats() = authenticated(Role.read).async(fieldsBodyParser) { implicit request =>
def stats() = authenticated(Role.read).async(fieldsBodyParser) { implicit request
val query = request.body.getValue("query").fold[QueryDef](QueryDSL.any)(_.as[QueryDef])
val aggs = request.body.getValue("stats").getOrElse(throw BadRequestError("Parameter \"stats\" is missing")).as[Seq[Agg]]
caseSrv.stats(query, aggs).map(s => Ok(s))
caseSrv.stats(query, aggs).map(s Ok(s))
}

@Timed
def linkedCases(id: String) = authenticated(Role.read).async { implicit request =>
def linkedCases(id: String) = authenticated(Role.read).async { implicit request
caseSrv.linkedCases(id)
.runWith(Sink.seq)
.map { cases =>
.map { cases
val casesList = cases.sortWith {
case ((c1, _), (c2, _)) => c1.startDate().after(c2.startDate())
case ((c1, _), (c2, _)) c1.startDate().after(c2.startDate())
}.map {
case (caze, artifacts) =>
case (caze, artifacts)
Json.toJson(caze).as[JsObject] - "description" +
("linkedWith" -> Json.toJson(artifacts)) +
("linksCount" -> Json.toJson(artifacts.size))
Expand All @@ -117,8 +122,8 @@ class CaseCtrl @Inject() (
}

@Timed
def merge(caseId1: String, caseId2: String) = authenticated(Role.read).async { implicit request =>
caseMergeSrv.merge(caseId1, caseId2).map { caze =>
def merge(caseId1: String, caseId2: String) = authenticated(Role.read).async { implicit request
caseMergeSrv.merge(caseId1, caseId2).map { caze
renderer.toOutput(OK, caze)
}
}
Expand Down
111 changes: 83 additions & 28 deletions thehive-backend/app/models/Case.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import play.api.libs.json.Json
import play.api.libs.json.Json.toJsFieldJsValueWrapper

import org.elastic4play.JsonFormat.dateFormat
import org.elastic4play.models.{ AttributeDef, AttributeFormat => F, AttributeOption => O, BaseEntity, EntityDef, HiveEnumeration, ModelDef }
import org.elastic4play.models.{ AttributeDef, AttributeFormat F, AttributeOption O, BaseEntity, EntityDef, HiveEnumeration, ModelDef }
import org.elastic4play.services.{ FindSrv, SequenceSrv }

import JsonFormat.{ caseImpactStatusFormat, caseResolutionStatusFormat, caseStatusFormat }
import services.AuditedModel
import services.CaseSrv
import play.api.Logger

object CaseStatus extends Enumeration with HiveEnumeration {
type Type = Value
Expand All @@ -34,7 +36,7 @@ object CaseImpactStatus extends Enumeration with HiveEnumeration {
val NoImpact, WithImpact, NotApplicable = Value
}

trait CaseAttributes { _: AttributeDef =>
trait CaseAttributes { _: AttributeDef
val caseId = attribute("caseId", F.numberFmt, "Id of the case (auto-generated)", O.model)
val title = attribute("title", F.textFmt, "Title of the case")
val description = attribute("description", F.textFmt, "Description of the case")
Expand All @@ -50,58 +52,111 @@ trait CaseAttributes { _: AttributeDef =>
val resolutionStatus = optionalAttribute("resolutionStatus", F.enumFmt(CaseResolutionStatus), "Resolution status of the case")
val impactStatus = optionalAttribute("impactStatus", F.enumFmt(CaseImpactStatus), "Impact status of the case")
val summary = optionalAttribute("summary", F.textFmt, "Summary of the case, to be provided when closing a case")
val mergeInto = optionalAttribute("mergeInto",F.stringFmt, "Id of the case created by the merge")
val mergeFrom = multiAttribute("mergeFrom",F.stringFmt, "Id of the cases merged")
val mergeInto = optionalAttribute("mergeInto", F.stringFmt, "Id of the case created by the merge")
val mergeFrom = multiAttribute("mergeFrom", F.stringFmt, "Id of the cases merged")
}

@Singleton
class CaseModel @Inject() (
artifactModel: Provider[ArtifactModel],
taskModel: Provider[TaskModel],
caseSrv: Provider[CaseSrv],
sequenceSrv: SequenceSrv,
findSrv: FindSrv,
implicit val ec: ExecutionContext) extends ModelDef[CaseModel, Case]("case") with CaseAttributes with AuditedModel { caseModel =>
implicit val ec: ExecutionContext) extends ModelDef[CaseModel, Case]("case") with CaseAttributes with AuditedModel { caseModel

lazy val logger = Logger(getClass)
override val defaultSortBy = Seq("-startDate")
override val removeAttribute = Json.obj("status" -> CaseStatus.Deleted)

override def creationHook(parent: Option[BaseEntity], attrs: JsObject) = {
sequenceSrv("case").map { caseId =>
sequenceSrv("case").map { caseId
attrs + ("caseId" -> JsNumber(caseId))
}
}

override def updateHook(entity: BaseEntity, updateAttrs: JsObject): Future[JsObject] = Future.successful {
(updateAttrs \ "status").asOpt[CaseStatus.Type] match {
case Some(CaseStatus.Resolved) if !updateAttrs.keys.contains("endDate") =>
case Some(CaseStatus.Resolved) if !updateAttrs.keys.contains("endDate")
updateAttrs + ("endDate" -> Json.toJson(new Date))
case Some(CaseStatus.Open) =>
case Some(CaseStatus.Open)
updateAttrs + ("endDate" -> JsArray(Nil))
case _ =>
case _
updateAttrs
}
}

override def getStats(entity: BaseEntity): Future[JsObject] = {
private[models] def buildArtifactStats(caze: Case): Future[JsObject] = {
import org.elastic4play.services.QueryDSL._
for {
taskStatsJson <- findSrv(
taskModel.get,
and(
"_parent" ~= entity.id,
"status" in ("Waiting", "InProgress", "Completed")),
groupByField("status", selectCount))
(taskCount, taskStats) = taskStatsJson.value.foldLeft((0L, JsObject(Nil))) {
case ((total, s), (key, value)) =>
val count = (value \ "count").as[Long]
(total + count, s + (key -> JsNumber(count)))
findSrv(
artifactModel.get,
and(
parent("case", withId(caze.id)),
"status" ~= "Ok"),
selectCount)
.map { artifactStats
Json.obj("artifacts" -> artifactStats)
}
}

private[models] def buildTaskStats(caze: Case): Future[JsObject] = {
import org.elastic4play.services.QueryDSL._
findSrv(
taskModel.get,
and(
parent("case", withId(caze.id)),
"status" in ("Waiting", "InProgress", "Completed")),
groupByField("status", selectCount))
.map { taskStatsJson
val (taskCount, taskStats) = taskStatsJson.value.foldLeft((0L, JsObject(Nil))) {
case ((total, s), (key, value))
val count = (value \ "count").as[Long]
(total + count, s + (key -> JsNumber(count)))
}
Json.obj("tasks" -> (taskStats + ("total" -> JsNumber(taskCount))))
}
}

private[models] def buildMergeIntoStats(caze: Case): Future[JsObject] = {
caze.mergeInto()
.fold(Future.successful(Json.obj())) { mergeCaseId
caseSrv.get.get(mergeCaseId).map { c
Json.obj("mergeInto" -> Json.obj(
"caseId" -> c.caseId(),
"title" -> c.title()))
}
}
artifactStats <- findSrv(
artifactModel.get,
and(
"_parent" ~= entity.id,
"status" ~= "Ok"),
selectCount)
} yield Json.obj("tasks" -> (taskStats + ("total" -> JsNumber(taskCount))), "artifacts" -> artifactStats)
}

private[models] def buildMergeFromStats(caze: Case): Future[JsObject] = {
Future
.traverse(caze.mergeFrom()) { id
caseSrv.get.get(id).map { c
Json.obj(
"caseId" -> c.caseId(),
"title" -> c.title())
}
}
.map {
case mf if !mf.isEmpty Json.obj("mergeFrom" -> mf)
case _ Json.obj()
}
}
override def getStats(entity: BaseEntity): Future[JsObject] = {


entity match {
case caze: Case
for {
taskStats <- buildTaskStats(caze)
artifactStats <- buildArtifactStats(caze)
mergeIntoStats <- buildMergeIntoStats(caze)
mergeFromStats <- buildMergeFromStats(caze)
} yield taskStats ++ artifactStats ++ mergeIntoStats ++ mergeFromStats
case other
logger.warn(s"Request caseStats from a non-case entity ?! ${other.getClass}:$other")
Future.successful(Json.obj())
}
}

override val computedMetrics = Map(
Expand Down

0 comments on commit f63d737

Please sign in to comment.