Skip to content

Commit

Permalink
Merge pull request #1862 from betagouv/master
Browse files Browse the repository at this point in the history
MEP
  • Loading branch information
eletallbetagouv authored Jan 28, 2025
2 parents 56f7724 + c64a423 commit fac5e2a
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 247 deletions.
17 changes: 17 additions & 0 deletions app/controllers/StaticController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package controllers

import authentication.Authenticator
import models.User
import play.api.mvc.ControllerComponents

import scala.concurrent.ExecutionContext
import scala.concurrent.Future

class StaticController(authenticator: Authenticator[User], controllerComponents: ControllerComponents)(implicit
val ec: ExecutionContext
) extends BaseController(authenticator, controllerComponents) {

def api = UserAwareAction.async(parse.empty) { _ =>
Future.successful(Ok(views.html.api()))
}
}
28 changes: 28 additions & 0 deletions app/controllers/StatisticController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,34 @@ class StatisticController(
)
}

def getPublicStatCount(publicStat: PublicStat) = IpRateLimitedAction2.async {
((publicStat.filter, publicStat.percentageBaseFilter) match {
case (filter, Some(percentageBaseFilter)) =>
statsOrchestrator.getReportCountPercentageWithinReliableDates(None, filter, percentageBaseFilter)
case (filter, _) =>
statsOrchestrator.getReportCount(None, filter)
}).map(curve => Ok(Json.toJson(curve)))
}

def getPublicStatCurve(publicStat: PublicStat) = IpRateLimitedAction2.async {
((publicStat.filter, publicStat.percentageBaseFilter) match {
case (filter, Some(percentageBaseFilter)) =>
statsOrchestrator.getReportsCountPercentageCurve(None, filter, percentageBaseFilter)
case (filter, _) =>
statsOrchestrator.getReportsCountCurve(None, filter)
}).map(curve => Ok(Json.toJson(curve)))
}

def getDelayReportReadInHours(companyId: Option[UUID]) = SecuredAction
.andThen(
WithRole(UserRole.AdminsAndReadOnlyAndCCRF)
)
.async {
statsOrchestrator
.getReadAvgDelay(companyId)
.map(count => Ok(Json.toJson(StatsValue(count.map(_.toHours.toInt)))))
}

def getDelayReportResponseInHours(companyId: Option[UUID]) = SecuredAction.async { request =>
statsOrchestrator
.getResponseAvgDelay(companyId: Option[UUID], request.identity.userRole)
Expand Down
8 changes: 8 additions & 0 deletions app/controllers/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import play.api.mvc.PathBindable
import play.api.mvc.QueryStringBindable
import play.api.mvc.Request
import cats.syntax.either._
import models.PublicStat
import models.extractUUID
import models.report.ReportFileOrigin
import models.report.ReportResponseType
Expand Down Expand Up @@ -90,6 +91,13 @@ package object controllers {
reportAdminActionType => reportAdminActionType.entryName
)

implicit val PublicStatQueryStringBindable: QueryStringBindable[PublicStat] =
QueryStringBindable.bindableString
.transform[PublicStat](
publicStat => PublicStat.withNameInsensitive(publicStat),
publicStat => publicStat.entryName
)

implicit class RequestOps[T <: JsValue](request: Request[T])(implicit ec: ExecutionContext) {
def parseBody[B](path: JsPath = JsPath())(implicit reads: Reads[B]) = request.body
.validate[B](path.read[B])
Expand Down
45 changes: 24 additions & 21 deletions app/loader/SignalConsoApplicationLoader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,8 @@ class SignalConsoComponents(
controllerComponents
)

val staticController = new StaticController(cookieAuthenticator, controllerComponents)

val statisticController = new StatisticController(statsOrchestrator, cookieAuthenticator, controllerComponents)

val subscriptionOrchestrator = new SubscriptionOrchestrator(subscriptionRepository)
Expand Down Expand Up @@ -946,37 +948,38 @@ class SignalConsoComponents(
new _root_.router.Routes(
httpErrorHandler,
new HealthController(controllerComponents),
assets,
staticController,
statisticController,
companyAccessController,
reportListController,
reportFileController,
reportController,
socialNetworkController,
barcodeController,
reportConsumerReviewController,
engagementController,
emailValidationController,
ratingController,
constantController,
reportListController,
bookmarkController,
asyncFileController,
eventsController,
reportFileController,
bookmarkController,
reportToExternalController,
dataEconomieController,
adminController,
statisticController,
companyAccessController,
asyncFileController,
constantController,
socialNetworkController,
mobileAppController,
authController,
accountController,
blacklistedEmailsController,
emailValidationController,
companyController,
importController,
reportBlockedNotificationController,
ratingController,
subscriptionController,
websiteController,
siretExtractorController,
reportedPhoneController,
mobileAppController,
reportToExternalController,
dataEconomieController,
signalConsoReviewController
reportBlockedNotificationController,
blacklistedEmailsController,
signalConsoReviewController,
siretExtractorController,
importController,
barcodeController,
engagementController,
assets
)

def scheduleTasks() = {
Expand Down
38 changes: 38 additions & 0 deletions app/models/Statistics.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package models

import enumeratum.EnumEntry
import enumeratum.PlayEnum
import models.report.ReportStatus.statusReadByPro
import models.report.ReportStatus.statusWithProResponse
import models.report.ReportFilter
import models.report.ReportFilter.allReportsFilter
import models.report.ReportFilter.transmittedReportsFilter
import models.report.ReportStatus

import java.time.LocalDate
import play.api.libs.json.Json
import play.api.libs.json.OFormat
Expand Down Expand Up @@ -28,3 +37,32 @@ case class ReportReviewStats(
object ReportReviewStats {
implicit val format: OFormat[ReportReviewStats] = Json.format[ReportReviewStats]
}

sealed abstract class PublicStat(val filter: ReportFilter, val percentageBaseFilter: Option[ReportFilter] = None)
extends EnumEntry

object PublicStat extends PlayEnum[PublicStat] {
lazy val values = findValues
case object PromesseAction extends PublicStat(ReportFilter(status = Seq(ReportStatus.PromesseAction)))
case object Reports extends PublicStat(allReportsFilter)
case object TransmittedPercentage
extends PublicStat(
transmittedReportsFilter,
Some(allReportsFilter)
)
case object ReadPercentage
extends PublicStat(
ReportFilter(status = statusReadByPro),
Some(transmittedReportsFilter)
)
case object ResponsePercentage
extends PublicStat(
ReportFilter(status = statusWithProResponse),
Some(ReadPercentage.filter)
)
case object WebsitePercentage
extends PublicStat(
ReportFilter(hasWebsite = Some(true)),
Some(allReportsFilter)
)
}
30 changes: 30 additions & 0 deletions app/orchestrators/StatsOrchestrator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import models.report.delete.ReportAdminActionType
import models.report.review.ResponseEvaluation
import orchestrators.StatsOrchestrator.computeStartingDate
import orchestrators.StatsOrchestrator.formatStatData
import orchestrators.StatsOrchestrator.restrictToReliableDates
import orchestrators.StatsOrchestrator.toPercentage
import repositories.accesstoken.AccessTokenRepositoryInterface
import repositories.event.EventRepositoryInterface
Expand Down Expand Up @@ -125,6 +126,17 @@ class StatsOrchestrator(
baseCount <- reportRepository.count(user, basePercentageFilter)
} yield toPercentage(count, baseCount)

def getReportCountPercentageWithinReliableDates(
user: Option[User],
filter: ReportFilter,
basePercentageFilter: ReportFilter
): Future[Int] =
getReportCountPercentage(
user,
restrictToReliableDates(filter),
restrictToReliableDates(basePercentageFilter)
)

def getReportsCountCurve(
user: Option[User],
reportFilter: ReportFilter,
Expand All @@ -137,6 +149,21 @@ class StatsOrchestrator(
case CurveTickDuration.Day => reportRepository.getDailyCount(user, reportFilter, ticks)
}

def getReportsCountPercentageCurve(
user: Option[User],
reportFilter: ReportFilter,
baseFilter: ReportFilter
): Future[Seq[CountByDate]] =
for {
rawCurve <- getReportsCountCurve(user, reportFilter)
baseCurve <- getReportsCountCurve(user, baseFilter)
} yield rawCurve.sortBy(_.date).zip(baseCurve.sortBy(_.date)).map { case (a, b) =>
CountByDate(
count = toPercentage(a.count, b.count),
date = a.date
)
}

def getReportsTagsDistribution(companyId: Option[UUID], user: User): Future[Map[ReportTag, Int]] =
reportRepository.getReportsTagsDistribution(companyId, user)

Expand Down Expand Up @@ -168,6 +195,9 @@ class StatsOrchestrator(
}
}

def getReadAvgDelay(companyId: Option[UUID] = None) =
eventRepository.getAvgTimeUntilEvent(ActionEvent.REPORT_READING_BY_PRO, companyId)

def getResponseAvgDelay(companyId: Option[UUID] = None, userRole: UserRole): Future[Option[Duration]] = {
val onlyProShareable = userRole == UserRole.Professionnel
eventRepository.getAvgTimeUntilEvent(
Expand Down
16 changes: 9 additions & 7 deletions app/repositories/website/WebsiteRepository.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ class WebsiteRepository(

import dbConfig._

override def validateAndCreate(newWebsite: Website): Future[Website] =
db.run(
table
override def validateAndCreate(newWebsite: Website): Future[Website] = db.run(
(for {
maybeWebsite <- table
.filter(_.host === newWebsite.host)
.filter { website =>
val hasBeenAlreadyIdentifiedByConso =
Expand All @@ -55,10 +55,12 @@ class WebsiteRepository(
}
.result
.headOption
).flatMap {
case Some(website) => Future.successful(website)
case None => create(newWebsite)
}
website <- maybeWebsite match {
case Some(website) => DBIO.successful(website)
case None => table returning table += newWebsite
}
} yield website).transactionally
)

override def searchValidAssociationByHost(host: String): Future[Seq[Website]] =
db.run(
Expand Down
59 changes: 59 additions & 0 deletions app/views/api.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
@()

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Description de l'API SignalConso</title>
</head>
<body>
<table border="1" cellpadding="10" width="100%">
<tr>
<th class="test">Chemin</th>
<th>Méthode</th>
<th>Description</th>
</tr>
<tr>
<td>/api/authenticate</td>
<td>POST</td>
<td>Authentification de l'utilisateur</td>
</tr>
<tr>
<td>/api/reports/:uuid</td>
<td>GET</td>
<td>Récupération d'un signalement par son identifiant</td>
</tr>
<tr>
<td>/api/reports</td>
<td>GET</td>
<td>Récupération des signalements par page</td>
</tr>
<tr>
<td>/api/reports</td>
<td>POST</td>
<td>Création d'un signalement</td>
</tr>
<tr>
<td>/api/events/:uuidReport</td>
<td>GET</td>
<td>Récupération des évènements d'un signalement</td>
</tr>

<tr>
<td>/api/reports/stats</td>
<td>GET</td>
<td>Récupération des statistiques sur les signalements</td>
</tr>
<tr>
<td>/api/companies</td>
<td>GET</td>
<td>Recherche textuelle d'entreprises</td>
</tr>
<tr>
<td>/api/companies/suggest</td>
<td>GET</td>
<td>Recherche textuelle de suggestions d'entreprises</td>
</tr>
</table>
</body>
</html>
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ routesImport ++= Seq(
"models.report.reportfile.ReportFileId",
"models.report.ReportResponseType",
"models.report.delete.ReportAdminActionType",
"models.PublicStat",
"controllers.IdentificationStatusQueryStringBindable",
"controllers.WebsiteIdPathBindable",
"controllers.UUIDPathBindable",
Expand All @@ -47,7 +48,8 @@ routesImport ++= Seq(
"controllers.ReportFileIdPathBindable",
"controllers.ReportResponseTypeQueryStringBindable",
"controllers.ReportAdminActionTypeQueryStringBindable",
"controllers.ReportFileOriginQueryStringBindable"
"controllers.ReportFileOriginQueryStringBindable",
"controllers.PublicStatQueryStringBindable"
)

semanticdbVersion := scalafixSemanticdb.revision
Expand Down
Loading

0 comments on commit fac5e2a

Please sign in to comment.