Skip to content

Commit

Permalink
Nouvelle page utilisateurs pour tout le monde + bump tabulator 5.5.0 (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
niladic authored Jul 17, 2023
1 parent 81f4b8a commit d4ec396
Show file tree
Hide file tree
Showing 14 changed files with 223 additions and 183 deletions.
10 changes: 0 additions & 10 deletions app/controllers/Operators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,6 @@ object Operators {
payload()
}

def asAdminWhoSeesUsersOfArea(areaId: UUID)(errorEventType: EventType, errorMessage: => String)(
payload: () => Future[Result]
)(implicit request: RequestWithUserData[_], ec: ExecutionContext): Future[Result] =
if (not(request.currentUser.admin) || not(request.currentUser.canSeeUsersInArea(areaId))) {
eventService.log(errorEventType, errorMessage)
Future(Unauthorized("Vous n'avez pas le droit de faire ça"))
} else {
payload()
}

def asUserWhoSeesUsersOfArea(areaId: UUID)(errorEventType: EventType, errorMessage: => String)(
payload: () => Future[Result]
)(implicit request: RequestWithUserData[_], ec: ExecutionContext): Future[Result] =
Expand Down
95 changes: 3 additions & 92 deletions app/controllers/UserController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import helper.{Time, UUIDHelper}
import javax.inject.{Inject, Singleton}
import models.EventType.{
AddUserError,
AllUserCSVUnauthorized,
AllUserCsvShowed,
AllUserIncorrectSetup,
AllUserUnauthorized,
CGUShowed,
Expand Down Expand Up @@ -64,7 +62,7 @@ import play.filters.csrf.CSRF
import play.filters.csrf.CSRF.Token
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
import serializers.{Keys, UserAndGroupCsvSerializer}
import serializers.Keys
import serializers.ApiModel.{SearchResult, UserGroupInfos, UserInfos}
import services._

Expand Down Expand Up @@ -234,7 +232,7 @@ case class UserController @Inject() (
val applications = applicationService.allByArea(selectedArea.id, anonymous = true)
eventService.log(UsersShowed, "Visualise la vue des utilisateurs")
val result = request.getQueryString(Keys.QueryParam.vue).getOrElse("nouvelle") match {
case "nouvelle" if request.currentUser.admin =>
case "nouvelle" =>
views.users.page(request.currentUser, request.rights, selectedArea)
case _ =>
views.html.allUsersByGroup(request.currentUser, request.rights)(
Expand All @@ -249,99 +247,12 @@ case class UserController @Inject() (
}
}

def allCSV(areaId: UUID): Action[AnyContent] =
loginAction.async { implicit request: RequestWithUserData[AnyContent] =>
asAdminWhoSeesUsersOfArea(areaId)(
AllUserCSVUnauthorized,
"Accès non autorisé à l'export utilisateur"
) { () =>
val area = Area.fromId(areaId).get
val usersFuture: Future[List[User]] = if (areaId === Area.allArea.id) {
if (Authorization.isAdmin(request.rights)) {
// Includes users without any group for debug purpose
userService.all
} else {
groupService.byAreas(request.currentUser.areas).map { groupsOfArea =>
userService.byGroupIds(groupsOfArea.map(_.id), includeDisabled = true)
}
}
} else {
groupService.byArea(areaId).map { groupsOfArea =>
userService.byGroupIds(groupsOfArea.map(_.id), includeDisabled = true)
}
}
val groupsFuture: Future[List[UserGroup]] =
groupService.byAreas(request.currentUser.areas)
eventService.log(AllUserCsvShowed, "Visualise le CSV de tous les zones de l'utilisateur")

usersFuture.zip(groupsFuture).map { case (users, groups) =>
def userToCSV(user: User): String = {
val userGroups = user.groupIds.flatMap(id => groups.find(_.id === id))
List[String](
user.id.toString,
user.firstName.orEmpty,
user.lastName.orEmpty,
user.email,
Time.formatPatternFr(user.creationDate, "dd-MM-YYYY-HHhmm"),
if (user.sharedAccount) "Compte Partagé" else " ",
if (user.sharedAccount) user.name else " ",
user.helperRoleName.getOrElse(""),
if (user.instructor) "Instructeur" else " ",
if (user.groupAdmin) "Responsable" else " ",
if (user.expert) "Expert" else " ",
if (user.admin) "Admin" else " ",
if (user.disabled) "Désactivé" else " ",
user.communeCode,
user.areas.flatMap(Area.fromId).map(_.name).mkString(", "),
userGroups.map(_.name).mkString(", "),
userGroups
.flatMap(_.organisation)
.map(_.shortName)
.mkString(", "),
if (user.cguAcceptationDate.nonEmpty) "CGU Acceptées" else "",
if (user.newsletterAcceptationDate.nonEmpty) "Newsletter Acceptée" else ""
).mkString(";")
}

val headers = List[String](
"Id",
UserAndGroupCsvSerializer.USER_FIRST_NAME.prefixes.head,
UserAndGroupCsvSerializer.USER_LAST_NAME.prefixes.head,
UserAndGroupCsvSerializer.USER_EMAIL.prefixes.head,
"Création",
UserAndGroupCsvSerializer.USER_ACCOUNT_IS_SHARED.prefixes.head,
UserAndGroupCsvSerializer.SHARED_ACCOUNT_NAME.prefixes.head,
"Aidant",
UserAndGroupCsvSerializer.USER_INSTRUCTOR.prefixes.head,
UserAndGroupCsvSerializer.USER_GROUP_MANAGER.prefixes.head,
"Expert",
"Admin",
"Actif",
"Commune INSEE",
UserAndGroupCsvSerializer.GROUP_AREAS_IDS.prefixes.head,
UserAndGroupCsvSerializer.GROUP_NAME.prefixes.head,
UserAndGroupCsvSerializer.GROUP_ORGANISATION.prefixes.head,
"CGU",
"Newsletter"
).mkString(";")

val csvContent = (List(headers) ++ users.map(userToCSV)).mkString("\n")
val date = Time.formatPatternFr(Time.nowParis(), "dd-MMM-YYY-HH'h'mm")
val filename = "aplus-" + date + "-users-" + area.name.replace(" ", "-") + ".csv"

Ok(csvContent)
.withHeaders("Content-Disposition" -> s"""attachment; filename="$filename"""")
.as("text/csv")
}
}
}

def search: Action[AnyContent] =
loginAction.async { implicit request =>
def toUserInfos(usersAndGroups: (List[User], List[UserGroup])): List[UserInfos] = {
val (users, groups) = usersAndGroups
val idToGroup = groups.map(group => (group.id, group)).toMap
users.map(user => UserInfos.fromUser(user, idToGroup))
users.map(user => UserInfos.fromUser(user, request.rights, idToGroup))
}
val area = request
.getQueryString(Keys.QueryParam.searchAreaId)
Expand Down
2 changes: 0 additions & 2 deletions app/models/EventType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ object EventType {
object AllAsShowed extends Info
object AllAsUnauthorized extends Warn
object AllCSVShowed extends Info
object AllUserCSVUnauthorized extends Warn
object AllUserCsvShowed extends Info
object AllUserIncorrectSetup extends Info
object AllUserUnauthorized extends Warn
object ApplicationCreated extends Info
Expand Down
13 changes: 12 additions & 1 deletion app/models/User.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import cats.syntax.all._
import constants.Constants
import helper.{Hash, Pseudonymizer, Time, UUIDHelper}
import helper.Time.zonedDateTimeInstance
import helper.StringHelper.{notLetterNorNumberRegex, withQuotes}
import helper.StringHelper.{
capitalizeName,
commonStringInputNormalization,
notLetterNorNumberRegex,
withQuotes
}

case class User(
id: UUID,
Expand Down Expand Up @@ -236,4 +241,10 @@ object User {
internalSupportComment = None
)

def standardName(firstName: String, lastName: String): String = {
val normalizedFirstName = commonStringInputNormalization(firstName)
val normalizedLastName = commonStringInputNormalization(lastName)
s"${normalizedLastName.toUpperCase} ${capitalizeName(normalizedFirstName)}"
}

}
29 changes: 19 additions & 10 deletions app/serializers/ApiModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -150,18 +150,25 @@ object ApiModel {
}

object UserInfos {
case class Group(id: UUID, name: String)
case class Group(id: UUID, name: String, currentUserCanEditGroup: Boolean)

implicit val userInfosGroupFormat: Format[UserInfos.Group] = Json.format[UserInfos.Group]
implicit val userInfosFormat: Format[UserInfos] = Json.format[UserInfos]

def fromUser(user: User, idToGroup: Map[UUID, UserGroup]): UserInfos = {
val completeName = {
val firstName = user.firstName.getOrElse("")
val lastName = user.lastName.getOrElse("")
if (firstName.nonEmpty || lastName.nonEmpty) s"${user.name} ($lastName $firstName)"
else user.name
}
def fromUser(
user: User,
rights: Authorization.UserRights,
idToGroup: Map[UUID, UserGroup]
): UserInfos = {
val completeName =
if (user.sharedAccount)
user.name
else {
val firstName = user.firstName.getOrElse("")
val lastName = user.lastName.getOrElse("")
if (firstName.nonEmpty || lastName.nonEmpty) User.standardName(firstName, lastName)
else user.name
}
UserInfos(
id = user.id,
firstName = user.firstName,
Expand All @@ -173,11 +180,13 @@ object ApiModel {
phoneNumber = user.phoneNumber,
helper = user.helperRoleName.nonEmpty,
instructor = user.instructorRoleName.nonEmpty,
areas = user.areas.flatMap(Area.fromId).map(_.toString),
areas = user.areas.flatMap(Area.fromId).map(_.toString).sorted,
groupNames = user.groupIds.flatMap(idToGroup.get).map(_.name),
groups = user.groupIds
.flatMap(idToGroup.get)
.map(group => UserInfos.Group(group.id, group.name)),
.map(group =>
UserInfos.Group(group.id, group.name, Authorization.canEditGroup(group)(rights))
),
groupEmails = user.groupIds.flatMap(idToGroup.get).flatMap(_.email),
groupAdmin = user.groupAdminRoleName.nonEmpty,
admin = user.adminRoleName.nonEmpty,
Expand Down
2 changes: 1 addition & 1 deletion app/services/UserService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ class UserService @Inject() (
val normalizedFirstName = firstName.normalized
val normalizedLastName = lastName.normalized
val normalizedQualite = qualite.normalized
val name = s"${normalizedLastName.toUpperCase} ${normalizedFirstName.capitalizeWords}"
val name = User.standardName(firstName, lastName)
SQL"""
UPDATE "user" SET
name = $name,
Expand Down
5 changes: 0 additions & 5 deletions app/views/allUsersByGroup.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,6 @@
}
</select>
</p>
}
@if(currentUser.admin) {
<button class="mdl-button mdl-js-button mdl-button--raised onclick-change-location" data-location="@routes.UserController.allCSV(selectedArea.id)">
Télécharger export CSV
</button>
}
@for(userGroup <- userGroups.sortBy(_.name)) {
@defining(allUsers.filter(_.groupIds.contains(userGroup.id))){ groupUsers =>
Expand Down
37 changes: 33 additions & 4 deletions app/views/users.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ object users {
mainInfos: MainInfos
): Html =
views.html.main(currentUser, currentUserRights, maxWidth = false)(
s"Gestion des groupes utilisateurs - ${selectedArea.name}"
s"Gestion des utilisateurs - ${selectedArea.name}"
)(
views.helpers.head.publicCss("stylesheets/newForm.css")
)(
Expand Down Expand Up @@ -75,14 +75,43 @@ object users {
)
),
div(id := "current-area-value", data("area-id") := selectedArea.id.toString),
div(
id := "user-role",
data("is-admin") := currentUser.admin.toString,
data("can-see-edit-user-page") := Authorization
.canSeeEditUserPage(currentUserRights)
.toString,
),
currentUser.admin.some.filter(identity).map(_ => searchForm()),
div(cls := "mdl-cell mdl-cell--12-col", id := "tabulator-users-table"),
div(cls := "mdl-cell mdl-cell--12-col", id := "tabulator-groups-table"),
currentUser.admin.some
.filter(identity)
.map(_ => views.addGroup.innerForm(currentUser, selectedArea))
.map(_ => div(cls := "mdl-cell mdl-cell--12-col", id := "tabulator-groups-table")),
currentUser.admin.some
.filter(identity)
.map(_ => views.addGroup.innerForm(currentUser, selectedArea)),
div(
cls := "mdl-cell",
a(
id := "users-download-btn-csv",
href := "#",
i(cls := "fas fa-download"),
" Téléchargement au format CSV"
)
),
div(
cls := "mdl-cell",
a(
id := "users-download-btn-xlsx",
href := "#",
i(cls := "fas fa-download"),
" Téléchargement au format XLSX"
)
),
)
)(Nil)
)(
views.helpers.head.publicScript("generated-js/xlsx.full.min.js")
)

def searchForm(): Tag =
div(
Expand Down
1 change: 0 additions & 1 deletion conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ GET /territoires/deploiement
GET /territoires/deploiement/france-service controllers.AreaController.franceServiceDeploymentDashboard
GET /territoires/deploiement/france-services controllers.AreaController.franceServices
GET /territoires/:areaId/utilisateurs controllers.UserController.all(areaId: java.util.UUID)
GET /territoires/:areaId/utilisateurs.csv controllers.UserController.allCSV(areaId: java.util.UUID)

# Check
GET /status controllers.HomeController.status
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"dialog-polyfill": "0.5.6",
"proxy-polyfill": "0.3.2",
"slim-select": "1.27.1",
"tabulator-tables": "5.2.7",
"tabulator-tables": "5.5.0",
"ts-polyfill": "3.8.2",
"unfetch": "4.2.0",
"unorm": "1.6.0",
Expand All @@ -22,7 +22,7 @@
"devDependencies": {
"@babel/core": "7.21.8",
"@babel/preset-env": "7.21.5",
"@types/tabulator-tables": "5.2.0",
"@types/tabulator-tables": "5.4.8",
"babel-loader": "9.1.2",
"copy-webpack-plugin": "11.0.0",
"css-loader": "6.7.4",
Expand Down
12 changes: 6 additions & 6 deletions typescript/src/admin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Tabulator, TabulatorFull } from 'tabulator-tables';
import { ColumnDefinition, Formatter, TabulatorFull } from 'tabulator-tables';
import "tabulator-tables/dist/css/tabulator.css";


Expand All @@ -19,7 +19,7 @@ const franceServiceDeploymentDownloadBtnXlsxId = "aplus-admin-deployment-france-


// TODO: macro that will generate field names from the scala case class (Scala => TS)
const franceServiceDeploymentColumns: Array<Tabulator.ColumnDefinition> = [
const franceServiceDeploymentColumns: Array<ColumnDefinition> = [
{ title: "Nom", field: "nomFranceService", sorter: "string", width: 200 },
{
title: "Département",
Expand Down Expand Up @@ -175,7 +175,7 @@ if (window.document.getElementById(deploymentTableTagId)) {
}
fetch(url).then((response) => response.json()).then((deploymentData: DeploymentData) => {

const organisationFormatter: Tabulator.Formatter = (cell) => {
const organisationFormatter: Formatter = (cell) => {
const value = cell.getValue();
const areaName = cell.getRow().getData().areaName;

Expand Down Expand Up @@ -208,7 +208,7 @@ if (window.document.getElementById(deploymentTableTagId)) {
const title = orgSet.organisations.map((organisation) => organisation.shortName).join(" / ");
const field = orgSet.id;

const column: Tabulator.ColumnDefinition = {
const column: ColumnDefinition = {
title,
field,
sorter: "number",
Expand All @@ -218,12 +218,12 @@ if (window.document.getElementById(deploymentTableTagId)) {
return column;
});

const areaColumn: Tabulator.ColumnDefinition =
const areaColumn: ColumnDefinition =
{
title: "Département", field: "areaName", sorter: "string", width: 150,
frozen: true
};
const totalColumn: Tabulator.ColumnDefinition =
const totalColumn: ColumnDefinition =
{
title: "Couverture",
field: "total",
Expand Down
Loading

0 comments on commit d4ec396

Please sign in to comment.