Skip to content

Commit f63d737

Browse files
author
To-om
committed
#14 Add merge information in stats ; add nstats parameter in get case api call
1 parent d3b576e commit f63d737

File tree

2 files changed

+110
-50
lines changed

2 files changed

+110
-50
lines changed

thehive-backend/app/controllers/Case.scala

+27-22
Original file line numberDiff line numberDiff line change
@@ -41,46 +41,51 @@ class CaseCtrl @Inject() (
4141
val log = Logger(getClass)
4242

4343
@Timed
44-
def create() = authenticated(Role.write).async(fieldsBodyParser) { implicit request =>
44+
def create() = authenticated(Role.write).async(fieldsBodyParser) { implicit request
4545
caseSrv.create(request.body)
46-
.map(caze => renderer.toOutput(CREATED, caze))
46+
.map(caze renderer.toOutput(CREATED, caze))
4747
}
4848

4949
@Timed
50-
def get(id: String) = authenticated(Role.read).async { implicit request =>
51-
caseSrv.get(id)
52-
.map(caze => renderer.toOutput(OK, caze))
50+
def get(id: String) = authenticated(Role.read).async(fieldsBodyParser) { implicit request
51+
val nparent = request.body.getLong("nparent").getOrElse(0L).toInt
52+
val withStats = request.body.getBoolean("nstats").getOrElse(false)
53+
54+
for {
55+
caze caseSrv.get(id)
56+
casesWithStats auxSrv.apply(caze, nparent, withStats)
57+
} yield renderer.toOutput(OK, casesWithStats)
5358
}
5459

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

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

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

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

7681
@Timed
77-
def delete(id: String) = authenticated(Role.write).async { implicit request =>
82+
def delete(id: String) = authenticated(Role.write).async { implicit request
7883
caseSrv.delete(id)
79-
.map(_ => NoContent)
84+
.map(_ NoContent)
8085
}
8186

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

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

102107
@Timed
103-
def linkedCases(id: String) = authenticated(Role.read).async { implicit request =>
108+
def linkedCases(id: String) = authenticated(Role.read).async { implicit request
104109
caseSrv.linkedCases(id)
105110
.runWith(Sink.seq)
106-
.map { cases =>
111+
.map { cases
107112
val casesList = cases.sortWith {
108-
case ((c1, _), (c2, _)) => c1.startDate().after(c2.startDate())
113+
case ((c1, _), (c2, _)) c1.startDate().after(c2.startDate())
109114
}.map {
110-
case (caze, artifacts) =>
115+
case (caze, artifacts)
111116
Json.toJson(caze).as[JsObject] - "description" +
112117
("linkedWith" -> Json.toJson(artifacts)) +
113118
("linksCount" -> Json.toJson(artifacts.size))
@@ -117,8 +122,8 @@ class CaseCtrl @Inject() (
117122
}
118123

119124
@Timed
120-
def merge(caseId1: String, caseId2: String) = authenticated(Role.read).async { implicit request =>
121-
caseMergeSrv.merge(caseId1, caseId2).map { caze =>
125+
def merge(caseId1: String, caseId2: String) = authenticated(Role.read).async { implicit request
126+
caseMergeSrv.merge(caseId1, caseId2).map { caze
122127
renderer.toOutput(OK, caze)
123128
}
124129
}

thehive-backend/app/models/Case.scala

+83-28
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ import play.api.libs.json.Json
1313
import play.api.libs.json.Json.toJsFieldJsValueWrapper
1414

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

1919
import JsonFormat.{ caseImpactStatusFormat, caseResolutionStatusFormat, caseStatusFormat }
2020
import services.AuditedModel
21+
import services.CaseSrv
22+
import play.api.Logger
2123

2224
object CaseStatus extends Enumeration with HiveEnumeration {
2325
type Type = Value
@@ -34,7 +36,7 @@ object CaseImpactStatus extends Enumeration with HiveEnumeration {
3436
val NoImpact, WithImpact, NotApplicable = Value
3537
}
3638

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

5759
@Singleton
5860
class CaseModel @Inject() (
5961
artifactModel: Provider[ArtifactModel],
6062
taskModel: Provider[TaskModel],
63+
caseSrv: Provider[CaseSrv],
6164
sequenceSrv: SequenceSrv,
6265
findSrv: FindSrv,
63-
implicit val ec: ExecutionContext) extends ModelDef[CaseModel, Case]("case") with CaseAttributes with AuditedModel { caseModel =>
66+
implicit val ec: ExecutionContext) extends ModelDef[CaseModel, Case]("case") with CaseAttributes with AuditedModel { caseModel
67+
68+
lazy val logger = Logger(getClass)
6469
override val defaultSortBy = Seq("-startDate")
6570
override val removeAttribute = Json.obj("status" -> CaseStatus.Deleted)
6671

6772
override def creationHook(parent: Option[BaseEntity], attrs: JsObject) = {
68-
sequenceSrv("case").map { caseId =>
73+
sequenceSrv("case").map { caseId
6974
attrs + ("caseId" -> JsNumber(caseId))
7075
}
7176
}
7277

7378
override def updateHook(entity: BaseEntity, updateAttrs: JsObject): Future[JsObject] = Future.successful {
7479
(updateAttrs \ "status").asOpt[CaseStatus.Type] match {
75-
case Some(CaseStatus.Resolved) if !updateAttrs.keys.contains("endDate") =>
80+
case Some(CaseStatus.Resolved) if !updateAttrs.keys.contains("endDate")
7681
updateAttrs + ("endDate" -> Json.toJson(new Date))
77-
case Some(CaseStatus.Open) =>
82+
case Some(CaseStatus.Open)
7883
updateAttrs + ("endDate" -> JsArray(Nil))
79-
case _ =>
84+
case _
8085
updateAttrs
8186
}
8287
}
8388

84-
override def getStats(entity: BaseEntity): Future[JsObject] = {
89+
private[models] def buildArtifactStats(caze: Case): Future[JsObject] = {
8590
import org.elastic4play.services.QueryDSL._
86-
for {
87-
taskStatsJson <- findSrv(
88-
taskModel.get,
89-
and(
90-
"_parent" ~= entity.id,
91-
"status" in ("Waiting", "InProgress", "Completed")),
92-
groupByField("status", selectCount))
93-
(taskCount, taskStats) = taskStatsJson.value.foldLeft((0L, JsObject(Nil))) {
94-
case ((total, s), (key, value)) =>
95-
val count = (value \ "count").as[Long]
96-
(total + count, s + (key -> JsNumber(count)))
91+
findSrv(
92+
artifactModel.get,
93+
and(
94+
parent("case", withId(caze.id)),
95+
"status" ~= "Ok"),
96+
selectCount)
97+
.map { artifactStats
98+
Json.obj("artifacts" -> artifactStats)
99+
}
100+
}
101+
102+
private[models] def buildTaskStats(caze: Case): Future[JsObject] = {
103+
import org.elastic4play.services.QueryDSL._
104+
findSrv(
105+
taskModel.get,
106+
and(
107+
parent("case", withId(caze.id)),
108+
"status" in ("Waiting", "InProgress", "Completed")),
109+
groupByField("status", selectCount))
110+
.map { taskStatsJson
111+
val (taskCount, taskStats) = taskStatsJson.value.foldLeft((0L, JsObject(Nil))) {
112+
case ((total, s), (key, value))
113+
val count = (value \ "count").as[Long]
114+
(total + count, s + (key -> JsNumber(count)))
115+
}
116+
Json.obj("tasks" -> (taskStats + ("total" -> JsNumber(taskCount))))
117+
}
118+
}
119+
120+
private[models] def buildMergeIntoStats(caze: Case): Future[JsObject] = {
121+
caze.mergeInto()
122+
.fold(Future.successful(Json.obj())) { mergeCaseId
123+
caseSrv.get.get(mergeCaseId).map { c
124+
Json.obj("mergeInto" -> Json.obj(
125+
"caseId" -> c.caseId(),
126+
"title" -> c.title()))
127+
}
97128
}
98-
artifactStats <- findSrv(
99-
artifactModel.get,
100-
and(
101-
"_parent" ~= entity.id,
102-
"status" ~= "Ok"),
103-
selectCount)
104-
} yield Json.obj("tasks" -> (taskStats + ("total" -> JsNumber(taskCount))), "artifacts" -> artifactStats)
129+
}
130+
131+
private[models] def buildMergeFromStats(caze: Case): Future[JsObject] = {
132+
Future
133+
.traverse(caze.mergeFrom()) { id
134+
caseSrv.get.get(id).map { c
135+
Json.obj(
136+
"caseId" -> c.caseId(),
137+
"title" -> c.title())
138+
}
139+
}
140+
.map {
141+
case mf if !mf.isEmpty Json.obj("mergeFrom" -> mf)
142+
case _ Json.obj()
143+
}
144+
}
145+
override def getStats(entity: BaseEntity): Future[JsObject] = {
146+
147+
148+
entity match {
149+
case caze: Case
150+
for {
151+
taskStats <- buildTaskStats(caze)
152+
artifactStats <- buildArtifactStats(caze)
153+
mergeIntoStats <- buildMergeIntoStats(caze)
154+
mergeFromStats <- buildMergeFromStats(caze)
155+
} yield taskStats ++ artifactStats ++ mergeIntoStats ++ mergeFromStats
156+
case other
157+
logger.warn(s"Request caseStats from a non-case entity ?! ${other.getClass}:$other")
158+
Future.successful(Json.obj())
159+
}
105160
}
106161

107162
override val computedMetrics = Map(

0 commit comments

Comments
 (0)