Skip to content

Commit

Permalink
TRELLO-2853 add endpoint to get most active users of company
Browse files Browse the repository at this point in the history
  • Loading branch information
eletallbetagouv committed Feb 4, 2025
1 parent cba388c commit d0bfb35
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 3 deletions.
6 changes: 6 additions & 0 deletions app/controllers/CompanyAccessController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ class CompanyAccessController(
.map(userWithAccessLevel => Ok(Json.toJson(userWithAccessLevel)))
}

def listAccessesMostActive(siret: String) = withCompanyAccess(siret).async { implicit request =>
companyAccessOrchestrator
.listAccessesMostActive(request.company, request.identity)
.map(mostActive => Ok(Json.toJson(mostActive)))
}

def countAccesses(siret: String) = withCompanyAccess(siret).async { implicit request =>
companyAccessOrchestrator
.listAccesses(request.company, request.identity)
Expand Down
3 changes: 2 additions & 1 deletion app/loader/SignalConsoApplicationLoader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ class SignalConsoComponents(
companyRepository,
accessTokenRepository,
companyActivationAttemptRepository,
eventRepository,
proAccessTokenOrchestrator
)

Expand Down Expand Up @@ -962,8 +963,8 @@ class SignalConsoComponents(
eventsController,
reportFileController,
adminController,
statisticController,
companyAccessController,
statisticController,
authController,
accountController,
blacklistedEmailsController,
Expand Down
23 changes: 23 additions & 0 deletions app/models/access/UserWithAccessLevel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,26 @@ object UserWithAccessLevel {
.withFieldConst(_.isHeadOffice, isHeadOffice)
.transform
}

case class UserWithAccessLevelAndNbResponse(
userId: UUID,
firstName: String,
lastName: String,
email: EmailAddress,
level: String,
editable: Boolean,
isHeadOffice: Boolean,
nbResponses: Int
)

object UserWithAccessLevelAndNbResponse {

implicit val UserWithAccessLevelAndNbResponsesWrites: Writes[UserWithAccessLevelAndNbResponse] =
Json.writes[UserWithAccessLevelAndNbResponse]

def build(access: UserWithAccessLevel, nbResponses: Int): UserWithAccessLevelAndNbResponse =
access
.into[UserWithAccessLevelAndNbResponse]
.withFieldConst(_.nbResponses, nbResponses)
.transform
}
21 changes: 20 additions & 1 deletion app/orchestrators/CompanyAccessOrchestrator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import controllers.error.AppError.TooMuchCompanyActivationAttempts
import models.AccessToken
import models.User
import models.access.ActivationLinkRequest
import models.access.UserWithAccessLevel
import models.access.UserWithAccessLevelAndNbResponse

import java.time.OffsetDateTime.now
import cats.implicits.catsSyntaxApplicativeId
Expand All @@ -19,7 +21,6 @@ import models.UserRole.DGCCRF
import models.UserRole.Professionnel
import models.UserRole.ReadOnlyAdmin
import models.UserRole.SuperAdmin
import models.access.UserWithAccessLevel
import models.access.UserWithAccessLevel.toApi
import models.company.AccessLevel
import models.company.Company
Expand All @@ -29,6 +30,9 @@ import repositories.accesstoken.AccessTokenRepositoryInterface
import repositories.company.CompanyRepositoryInterface
import repositories.companyaccess.CompanyAccessRepositoryInterface
import repositories.companyactivationattempt.CompanyActivationAttemptRepositoryInterface
import repositories.event.EventFilter
import repositories.event.EventRepositoryInterface
import utils.Constants.ActionEvent.REPORT_PRO_RESPONSE
import utils.SIREN
import utils.SIRET

Expand All @@ -42,6 +46,7 @@ class CompanyAccessOrchestrator(
val companyRepository: CompanyRepositoryInterface,
val accessTokenRepository: AccessTokenRepositoryInterface,
val companyActivationAttemptRepository: CompanyActivationAttemptRepositoryInterface,
val eventsRepository: EventRepositoryInterface,
val accessesOrchestrator: ProAccessTokenOrchestrator
)(implicit val ec: ExecutionContext) {

Expand Down Expand Up @@ -133,6 +138,20 @@ class CompanyAccessOrchestrator(
} yield filteredHeadOfficeAccess.getOrElse(List.empty) ++ subsidiaryUserAccess
}

def listAccessesMostActive(company: Company, user: User): Future[List[UserWithAccessLevelAndNbResponse]] =
for {
accesses <- listAccesses(company, user)
nbResponsesByUserIds <- eventsRepository.countCompanyEventsByUsers(
companyId = company.id,
usersIds = accesses.map(_.userId),
EventFilter(action = Some(REPORT_PRO_RESPONSE))
)
accessesWithNbResponses = accesses.map(access =>
UserWithAccessLevelAndNbResponse.build(access, nbResponsesByUserIds.getOrElse(access.userId, 0))
)
mostActive = accessesWithNbResponses.sortBy(-_.nbResponses).take(3)
} yield mostActive

private def getHeadOffice(company: Company): Future[Option[Company]] =
companyRepository
.findHeadOffices(List(SIREN.fromSIRET(company.siret)), openOnly = false)
Expand Down
18 changes: 18 additions & 0 deletions app/repositories/event/EventRepository.scala
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,24 @@ class EventRepository(
.result
}

override def countCompanyEventsByUsers(
companyId: UUID,
usersIds: List[UUID],
filter: EventFilter
): Future[Map[UUID, Int]] =
db.run {
getRawEvents(filter)
.filter(_.companyId === companyId)
.filter(
_.userId inSetBind usersIds
)
.groupBy(_.userId)
.map { case (userId, events) =>
userId.get -> events.length
}
.result
}.map(_.toMap)

override def getReportResponseReviews(companyId: Option[UUID]): Future[Seq[Event]] =
db.run(
table
Expand Down
1 change: 1 addition & 0 deletions app/repositories/event/EventRepositoryInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ trait EventRepositoryInterface extends CRUDRepositoryInterface[Event] {

def getCompanyEventsWithUsers(companyId: UUID, filter: EventFilter): Future[List[(Event, Option[User])]]

def countCompanyEventsByUsers(companyId: UUID, usersIds: List[UUID], filter: EventFilter): Future[Map[UUID, Int]]
def getReportResponseReviews(companyId: Option[UUID]): Future[Seq[Event]]

def fetchEventsOfReports(reports: List[Report]): Future[Map[UUID, List[Event]]]
Expand Down
2 changes: 1 addition & 1 deletion conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ GET /api/albert/classification/:reportId controller
###############################

GET /api/companies/:siret/events controllers.EventsController.getCompanyEvents(siret: SIRET, eventType: Option[String])
# various stats for the company page
GET /api/accesses/:siret/most-active controllers.CompanyAccessController.listAccessesMostActive(siret: String)
GET /api/stats/reports/delay/responsed controllers.StatisticController.getDelayReportResponseInHours(companyId: Option[java.util.UUID])
GET /api/stats/reports/tags controllers.StatisticController.getReportsTagsDistribution(companyId: Option[java.util.UUID])
GET /api/stats/reports/status controllers.StatisticController.getReportsStatusDistribution(companyId: Option[java.util.UUID])
Expand Down

0 comments on commit d0bfb35

Please sign in to comment.