Skip to content

Commit

Permalink
Change le tableau des demandes admin (#1352)
Browse files Browse the repository at this point in the history
  • Loading branch information
niladic authored Mar 28, 2022
1 parent 0e7e84a commit 7434f13
Show file tree
Hide file tree
Showing 18 changed files with 624 additions and 202 deletions.
122 changes: 71 additions & 51 deletions app/controllers/ApplicationController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ import play.api.cache.AsyncCacheApi
import play.api.data.Forms._
import play.api.data._
import play.api.data.validation.Constraints._
import play.api.libs.json.Json
import play.api.libs.ws.WSClient
import play.api.mvc._
import play.twirl.api.Html
import serializers.Keys
import serializers.ApiModel.{ApplicationMetadata, ApplicationMetadataResult}
import services._
import views.stats.StatsData

Expand Down Expand Up @@ -419,17 +421,28 @@ case class ApplicationController @Inject() (
applicationService.allForAreas(List(area.id), numOfMonthsDisplayed.some)
case (false, None) if user.groupAdmin =>
val userIds = userService.byGroupIds(user.groupIds, includeDisabled = true).map(_.id)
applicationService.allForUserIds(userIds)
applicationService.allForUserIds(userIds, numOfMonthsDisplayed.some)
case (false, Some(area)) if user.groupAdmin =>
val userGroupIds =
userGroupService.byIds(user.groupIds).filter(_.areaIds.contains[UUID](area.id)).map(_.id)
val userIds = userService.byGroupIds(userGroupIds, includeDisabled = true).map(_.id)
applicationService.allForUserIds(userIds)
val userIds = userService.byGroupIds(user.groupIds, includeDisabled = true).map(_.id)
applicationService
.allForUserIds(userIds, numOfMonthsDisplayed.some)
.map(_.filter(application => application.area === area.id))
case _ =>
Future(Nil)
Future.successful(Nil)
}

def all(areaId: UUID): Action[AnyContent] =
private def extractApplicationsAdminQuery(implicit
request: RequestWithUserData[_]
): (Option[Area], Int) = {
val areaOpt = areaInQueryString.filterNot(_.id === Area.allArea.id)
val numOfMonthsDisplayed: Int = request
.getQueryString(Keys.QueryParam.numOfMonthsDisplayed)
.flatMap(s => Try(s.toInt).toOption)
.getOrElse(3)
(areaOpt, numOfMonthsDisplayed)
}

def applicationsAdmin: Action[AnyContent] =
loginAction.async { implicit request =>
(request.currentUser.admin, request.currentUser.groupAdmin) match {
case (false, false) =>
Expand All @@ -443,29 +456,60 @@ case class ApplicationController @Inject() (
)
)
case _ =>
val numOfMonthsDisplayed: Int = request
.getQueryString(Keys.QueryParam.numOfMonthsDisplayed)
.flatMap(s => Try(s.toInt).toOption)
.getOrElse(3)
val area = if (areaId === Area.allArea.id) None else Area.fromId(areaId)
allApplicationVisibleByUserAdmin(request.currentUser, area, numOfMonthsDisplayed).map {
unfilteredApplications =>
val filteredApplications =
request.getQueryString(Keys.QueryParam.filterIsOpen) match {
case Some(_) => unfilteredApplications.filterNot(_.closed)
case None => unfilteredApplications
}
val (areaOpt, numOfMonthsDisplayed) = extractApplicationsAdminQuery
eventService.log(
AllApplicationsShowed,
s"Accède à la page des métadonnées des demandes [$areaOpt ; $numOfMonthsDisplayed]"
)
Future(
Ok(
views.applicationsAdmin
.page(request.currentUser, request.rights, areaOpt, numOfMonthsDisplayed)
)
)
}
}

def applicationsMetadata: Action[AnyContent] =
loginAction.async { implicit request =>
(request.currentUser.admin, request.currentUser.groupAdmin) match {
case (false, false) =>
eventService.log(
AllApplicationsUnauthorized,
"Liste des metadata des demandes non autorisée"
)
Future.successful(Unauthorized(Json.toJson(ApplicationMetadataResult(Nil))))
case _ =>
val (areaOpt, numOfMonthsDisplayed) = extractApplicationsAdminQuery
allApplicationVisibleByUserAdmin(request.currentUser, areaOpt, numOfMonthsDisplayed).map {
applications =>
eventService.log(
AllApplicationsShowed,
s"Visualise la liste des demandes de $areaId - taille = ${filteredApplications.size}"
"Accède à la liste des metadata des demandes " +
s"[territoire ${areaOpt.map(_.name).getOrElse("tous")} ; " +
s"taille : ${applications.size}]"
)
Ok(
views.html
.allApplications(request.currentUser, request.rights)(
filteredApplications,
area.getOrElse(Area.allArea)
val userIds: List[UUID] = (applications.flatMap(_.invitedUsers.keys) ++
applications.map(_.creatorUserId)).toList.distinct
val users = userService.byIds(userIds, includeDisabled = true)
val groupIds =
(users.flatMap(_.groupIds) ::: applications.flatMap(application =>
application.invitedGroupIdsAtCreation ::: application.answers.flatMap(
_.invitedGroupIds
)
)).distinct
val groups = userGroupService.byIds(groupIds)
val idToUser = users.map(user => (user.id, user)).toMap
val idToGroup = groups.map(group => (group.id, group)).toMap
val metadata = applications.map(application =>
ApplicationMetadata.fromApplication(
application,
request.rights,
idToUser,
idToGroup
)
)
Ok(Json.toJson(ApplicationMetadataResult(metadata)))
}
}
}
Expand Down Expand Up @@ -532,7 +576,7 @@ case class ApplicationController @Inject() (
): Future[(List[User], List[Application])] =
for {
users <- userService.byGroupIdsAnonymous(groups.map(_.id))
applications <- applicationService.allForUserIds(users.map(_.id))
applications <- applicationService.allForUserIds(users.map(_.id), none)
} yield (users, applications)

private def generateStats(
Expand Down Expand Up @@ -562,7 +606,7 @@ case class ApplicationController @Inject() (
.map(_.filter(_.areaIds.intersect(areaIds).nonEmpty))
users <- userService.byGroupIdsAnonymous(groups.map(_.id))
applications <- applicationService
.allForUserIds(users.map(_.id))
.allForUserIds(users.map(_.id), none)
.map(_.filter(application => areaIds.contains(application.area)))
} yield (users, applications)
case (_, _ :: _, _) =>
Expand Down Expand Up @@ -824,30 +868,6 @@ case class ApplicationController @Inject() (
.as("text/csv")
}

def allCSV(areaId: UUID): Action[AnyContent] =
loginAction.async { implicit request =>
val area = if (areaId === Area.allArea.id) Option.empty else Area.fromId(areaId)
val exportedApplicationsFuture =
if (request.currentUser.admin || request.currentUser.groupAdmin) {
allApplicationVisibleByUserAdmin(request.currentUser, area, 24)
} else {
Future(Nil)
}

exportedApplicationsFuture.map { exportedApplications =>
val date = Time.formatPatternFr(Time.nowParis(), "YYY-MM-dd-HH'h'mm")
val csvContent = applicationsToCSV(exportedApplications)

eventService.log(AllCSVShowed, s"Visualise un CSV pour la zone $area")
val filenameAreaPart: String = area.map(_.name.stripSpecialChars).getOrElse("tous")
Ok(csvContent)
.withHeaders(
"Content-Disposition" -> s"""attachment; filename="aplus-demandes-$date-$filenameAreaPart.csv""""
)
.as("text/csv")
}
}

private def usersWhoCanBeInvitedOn(application: Application, currentAreaId: UUID)(implicit
request: RequestWithUserData[_]
): Future[List[User]] =
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/GroupController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ case class GroupController @Inject() (
for {
groups <- groupService.byIdsFuture(user.groupIds)
users <- userService.byGroupIdsFuture(groups.map(_.id), includeDisabled = true)
applications <- applicationService.allForUserIds(users.map(_.id))
applications <- applicationService.allForUserIds(users.map(_.id), none)
} yield {
eventService.log(EventType.EditMyGroupShowed, "Visualise la modification de ses groupes")
Ok(
Expand All @@ -431,7 +431,7 @@ case class GroupController @Inject() (
eventService.log(EditGroupShowed, s"Visualise la vue de modification du groupe")
val isEmpty = groupService.isGroupEmpty(group.id)
applicationService
.allForUserIds(groupUsers.map(_.id))
.allForUserIds(groupUsers.map(_.id), none)
.map(applications =>
Ok(
views.html.editGroup(request.currentUser, request.rights)(
Expand Down
4 changes: 3 additions & 1 deletion app/controllers/JavascriptController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ class JavascriptController() extends InjectedController {
routes.javascript.ApiController.deploymentData,
routes.javascript.GroupController.deleteUnusedGroupById,
routes.javascript.GroupController.editGroup,
routes.javascript.ApplicationController.all,
routes.javascript.ApplicationController.applicationsAdmin,
routes.javascript.ApplicationController.applicationsMetadata,
routes.javascript.ApplicationController.show,
routes.javascript.UserController.all,
routes.javascript.UserController.deleteUnusedUserById,
routes.javascript.UserController.editUser,
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/PathValidator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ object PathValidator {
routes.AreaController.all,
routes.AreaController.deploymentDashboard,
routes.AreaController.franceServiceDeploymentDashboard,
routes.ApplicationController.all(placeholderUUID),
routes.ApplicationController.applicationsAdmin,
routes.UserController.all(placeholderUUID),
)
val uuidRegex = "([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})"
Expand Down
3 changes: 1 addition & 2 deletions app/filters/SentryFilter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ class SentryFilter @Inject() (implicit val mat: Materializer, ec: ExecutionConte
val queryParamsWhitelist = List(
Keys.QueryParam.vue,
Keys.QueryParam.uniquementFs,
Keys.QueryParam.numOfMonthsDisplayed,
Keys.QueryParam.filterIsOpen
Keys.QueryParam.numOfMonthsDisplayed
)

def apply(
Expand Down
111 changes: 110 additions & 1 deletion app/serializers/ApiModel.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package serializers

import cats.syntax.all._
import helper.Time
import java.time.Instant
import java.util.UUID
import models.{Area, Organisation, User, UserGroup}
import models.{Application, Area, Authorization, Organisation, User, UserGroup}
import play.api.libs.json._

object ApiModel {
Expand Down Expand Up @@ -168,4 +170,111 @@ object ApiModel {
implicit val searchResultFormat = Json.format[SearchResult]
}

// Embedded classes are here to avoid the 22 fields limit in Play Json
case class ApplicationMetadata(
id: UUID,
creationDateFormatted: String,
creationDay: String,
creatorUserName: String,
creatorUserId: UUID,
areaName: String,
pertinence: String,
internalId: Int,
closed: Boolean,
usefulness: String,
closedDateFormatted: Option[String],
closedDay: Option[String],
status: String,
currentUserCanSeeAnonymousApplication: Boolean,
creatorGroupNames: String,
invitedGroupNames: String,
stats: ApplicationMetadata.Stats,
)

object ApplicationMetadata {

case class Stats(
numberOfInvitedUsers: Int,
numberOfMessages: Int,
numberOfAnswers: Int,
firstAnswerTimeInMinutes: String,
resolutionTimeInMinutes: String,
firstAnswerTimeInDays: String,
resolutionTimeInDays: String,
)

implicit val applicationMetadataStatsWrites = Json.writes[ApplicationMetadata.Stats]
implicit val applicationMetadataWrites = Json.writes[ApplicationMetadata]

def fromApplication(
application: Application,
rights: Authorization.UserRights,
idToUser: Map[UUID, User],
idToGroup: Map[UUID, UserGroup]
) = {
val areaName = Area.fromId(application.area).map(_.name).getOrElse("Sans territoire")
val pertinence = if (!application.irrelevant) "Oui" else "Non"
val creatorUser = idToUser.get(application.creatorUserId)
val creatorUserGroupNames = creatorUser.toList
.flatMap(_.groupIds)
.distinct
.flatMap(idToGroup.get)
.map(_.name)
.mkString(",")
val invitedGroupNames = application.answers
.flatMap(_.invitedGroupIds)
.distinct
.flatMap(idToGroup.get)
.map(_.name)
.mkString(",")
ApplicationMetadata(
id = application.id,
creationDateFormatted = Time.formatForAdmins(application.creationDate.toInstant),
creationDay = Time.formatPatternFr(application.creationDate, "YYY-MM-dd"),
creatorUserName = application.creatorUserName,
creatorUserId = application.creatorUserId,
areaName = areaName,
pertinence = pertinence,
internalId = application.internalId,
closed = application.closed,
usefulness = application.usefulness.getOrElse(""),
closedDateFormatted =
application.closedDate.map(date => Time.formatForAdmins(date.toInstant)),
closedDay = application.closedDate.map(date =>
Time.formatPatternFr(application.creationDate, "YYY-MM-dd")
),
status = application.status.show,
currentUserCanSeeAnonymousApplication =
Authorization.canSeeApplication(application)(rights),
creatorGroupNames = creatorUserGroupNames,
invitedGroupNames = invitedGroupNames,
stats = ApplicationMetadata.Stats(
numberOfInvitedUsers = application.invitedUsers.size,
numberOfMessages = application.answers.length + 1,
numberOfAnswers =
application.answers.count(_.creatorUserID =!= application.creatorUserId),
firstAnswerTimeInMinutes =
application.firstAnswerTimeInMinutes.map(_.toString).getOrElse(""),
resolutionTimeInMinutes =
application.resolutionTimeInMinutes.map(_.toString).getOrElse(""),
firstAnswerTimeInDays = application.firstAnswerTimeInMinutes
.map(_.toDouble / (60.0 * 24.0))
.map(days => f"$days%.2f".reverse.dropWhile(_ === '0').reverse.stripSuffix("."))
.getOrElse(""),
resolutionTimeInDays = application.resolutionTimeInMinutes
.map(_.toDouble / (60.0 * 24.0))
.map(days => f"$days%.2f".reverse.dropWhile(_ === '0').reverse.stripSuffix("."))
.getOrElse("")
)
)
}

}

case class ApplicationMetadataResult(applications: List[ApplicationMetadata])

object ApplicationMetadataResult {
implicit val applicationMetadataResultWrites = Json.writes[ApplicationMetadataResult]
}

}
1 change: 0 additions & 1 deletion app/serializers/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ object Keys {
val areaId: String = "areaId"
val action: String = "action"
val uniquementFs: String = "uniquement-fs"
val filterIsOpen: String = "filtre-ouverte"

// Users

Expand Down
Loading

0 comments on commit 7434f13

Please sign in to comment.