Skip to content

Commit

Permalink
fix #1490 improve satisfaction stat query + show avg rating number
Browse files Browse the repository at this point in the history
  • Loading branch information
ih09ccbl authored and vsct-jburet committed Jul 20, 2023
1 parent 077b5ca commit a97253f
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 96 deletions.
3 changes: 1 addition & 2 deletions bot/admin/server/src/main/kotlin/BotAdminService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,7 @@ object BotAdminService {
}

fun searchRating(query: DialogsSearchQuery): RatingReportQueryResult? {
val res = dialogReportDAO.findBotDialogStats(query.toDialogReportQuery()) ?: return null
return res.copy(ratingDetails = dialogReportDAO.findBotDialogStatsByRating(query.toDialogReportQuery()))
return dialogReportDAO.findBotDialogStats(query.toDialogReportQuery())
}

fun deleteApplicationConfiguration(conf: BotApplicationConfiguration) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,16 @@
padding: 0;
overflow: visible;
}

.rating-circle {
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #e6e6e6;
font-weight: bold;
font-size: 24px;
color: #333;
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,30 @@
<nb-card *ngIf="satisfactionStat"
class="stats"
>
<div class="space space-component3">
<h5 class="heading1 text-basic">
<nb-icon *ngFor="let _ of [].constructor(satisfactionStat.ratingBot)" icon="star-outline"></nb-icon>
</h5>
<span class="text text-hint caption">

{{satisfactionStat.nbUsersRated}} users</span
>
</div>
</nb-card>

<div style="display: flex" *ngIf="satisfactionStat">
<nb-card
class="stats" *ngFor="let index of [1,2,3,4,5]"
>
<ng-container *ngIf="satisfactionStat">
<nb-card class="stats">
<div class="space space-component3">
<h5 class="heading1 text-basic">
<nb-icon *ngFor="let _ of [].constructor(index)" icon="star-outline"></nb-icon>
<nb-icon *ngFor="let _ of getStarArray()" icon="star-outline"></nb-icon>
</h5>
<span class="text text-hint caption">

{{getNbUsersByNote(index)}} users</span
>
<span class="text text-hint caption">{{satisfactionStat.nbUsersRated}} users</span>
</div>
<div class="space icon-space">
<nb-toggle [value]="index" (change)="updateRatingFilter($event)"
status="basic"
></nb-toggle>
<div class="space">
<div class="rating-circle" [ngStyle]="getStyles()">
<span>{{ satisfactionStat.ratingBot.toFixed(1) }}</span>
</div>
</div>
</nb-card>
</div>

<div style="display: flex">
<nb-card class="stats" *ngFor="let index of [1,2,3,4,5]">
<div class="space space-component3">
<h5 class="heading1 text-basic">
<nb-icon *ngFor="let _ of [].constructor(index)" icon="star-outline"></nb-icon>
</h5>
<span class="text text-hint caption">{{getNbUsersByNote(index)}} users</span>
</div>
<div class="space icon-space">
<nb-toggle [value]="index" (change)="updateRatingFilter($event)" status="basic"></nb-toggle>
</div>
</nb-card>
</div>
</ng-container>
<tock-dialogs [ratingFilter]="ratingFilter"></tock-dialogs>
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,32 @@ export class SatisfactionDetailsComponent implements OnInit {
const res = this.satisfactionStat.ratingDetails.find(it => it.rating == note)
return res ? res.nbUsers : 0;
}


getStarArray(): any[] {
const roundedRating = Math.round(this.satisfactionStat.ratingBot);
return Array(roundedRating).fill(0);
}


getStyles() {
const percent = this.satisfactionStat.ratingBot / 5 * 100;
return {
background: `conic-gradient(${this.getColorFromRating(this.satisfactionStat.ratingBot)} ${percent}%, #e6e6e6 ${percent}% 100%)`
};
}

getColorFromRating(rating: number): string {
const maxRating = 5; // Maximum rating
const minHue = 0; // Minimum hue (red)
const maxHue = 120; // Maximum hue (green)

// Calculate the hue based on the rating (from 0 to 120)
const hue = (rating / maxRating) * (maxHue - minHue) + minHue;

// Convert the hue to a CSS color
return `hsl(${hue}, 70%, 50%)`;
}

}

2 changes: 1 addition & 1 deletion bot/engine/src/main/kotlin/admin/dialog/DialogRating.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ data class DialogRating(
* [rating] is the rating given by the user
* the rating can be a number between 1 and 5
*/
var rating: Int? = null,
var rating: Double? = null,


/**
Expand Down
2 changes: 0 additions & 2 deletions bot/engine/src/main/kotlin/admin/dialog/DialogReportDAO.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ interface DialogReportDAO {

fun findBotDialogStats(query: DialogReportQuery): RatingReportQueryResult?

fun findBotDialogStatsByRating(query: DialogReportQuery): List<DialogRating>

fun getDialog(id: Id<Dialog>): DialogReport?

fun getNlpCallStats(actionId: Id<Action>, namespace: String): NlpCallStats?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ data class RatingReportQueryResult(
* the average rating must be between 1 and 5
* it can be null if no user has rated the bot or satisfaction is not activated
*/
val ratingBot: Int?,
val ratingBot: Double?,

/**
* [nbUsersRated] the number of users who rated the bot
Expand Down
2 changes: 1 addition & 1 deletion bot/storage-mongo/src/main/kotlin/DialogCol.kt
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ internal data class DialogCol(
@Data(internal = true)
@JacksonData(internal = true)
data class ParseRequestSatisfactionStatCol(
val rating: Int,
val rating: Double,
val count: Int = 1
) {

Expand Down
89 changes: 28 additions & 61 deletions bot/storage-mongo/src/main/kotlin/UserTimelineMongoDAO.kt
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep
DialogCol_.LastUpdateDate,
indexOptions = ttlIndexOptions
)
dialogCol.ensureIndex(
DialogCol_.ApplicationIds,
DialogCol_.Namespace,
DialogCol_.Rating,
Test
)
dialogCol.ensureIndex(
orderBy(
mapOf(
Expand Down Expand Up @@ -546,14 +552,14 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep
val filter = and(
DialogCol_.ApplicationIds `in` applicationsIds.filter { it.isNotEmpty() },
Namespace eq query.namespace,
if (query.playerId != null || query.displayTests) null else DialogCol_.Test eq false,
if (query.playerId != null || query.displayTests) null else Test eq false,
if (query.playerId == null) null else PlayerIds.id eq query.playerId!!.id,
if (dialogIds.isEmpty()) null else _id `in` dialogIds,
if (from == null) null else DialogCol_.LastUpdateDate gt from?.toInstant(),
if (to == null) null else DialogCol_.LastUpdateDate lt to?.toInstant(),
if (connectorType == null) null else Stories.actions.state.targetConnectorType.id eq connectorType!!.id,
if (query.intentName.isNullOrBlank()) null else Stories.currentIntent.name_ eq query.intentName,
if (!query.ratings.isNullOrEmpty()) DialogCol_.Rating `in` query.ratings!!.toSet() else null
if (query.ratings.isNotEmpty()) DialogCol_.Rating `in` query.ratings.toSet() else null
)
logger.debug("dialog search query: $filter")
val c = dialogCol.withReadPreference(secondaryPreferred())
Expand All @@ -574,68 +580,29 @@ internal object UserTimelineMongoDAO : UserTimelineDAO, UserReportDAO, DialogRep
}
}

override fun findBotDialogStatsByRating(query: DialogReportQuery): List<DialogRating> {
return with(query) {
val applicationsIds = getApplicationIds(query.namespace, query.nlpModel)
dialogCol.aggregate<ParseRequestSatisfactionStatCol>(
match(
and(
DialogCol_.ApplicationIds `in` applicationsIds.filter { it.isNotEmpty() },
Namespace eq query.namespace,
if (query.playerId != null || query.displayTests) null else DialogCol_.Test eq false,
if (query.playerId == null) null else PlayerIds.id eq query.playerId!!.id,
if (query.dialogId == null) null else _id eq query.dialogId!!.toId(),
if (from == null) null else DialogCol_.LastUpdateDate gt from?.toInstant(),
if (to == null) null else DialogCol_.LastUpdateDate lt to?.toInstant(),
if (connectorType == null) null else Stories.actions.state.targetConnectorType.id eq connectorType!!.id,
if (query.intentName.isNullOrBlank()) null else Stories.currentIntent.name_ eq query.intentName,
DialogCol_.Rating `in` setOf(1, 2, 3, 4, 5)
)
),
group(
DialogCol_.Rating from DialogCol_.Rating,
ParseRequestSatisfactionStatCol_.Count sum 1,
ParseRequestSatisfactionStatCol_.Rating avg ParseRequestSatisfactionStatCol_.Rating
),
sort(
ascending(
ParseRequestSatisfactionStatCol_.Rating,
)
)
).map { DialogRating(it.rating, it.count) }.toList()
}
}

override fun findBotDialogStats(query: DialogReportQuery): RatingReportQueryResult? {
return with(query) {
val applicationsIds = getApplicationIds(query.namespace, query.nlpModel)
dialogCol.aggregate<ParseRequestSatisfactionStatCol>(
match(
and(
DialogCol_.ApplicationIds `in` applicationsIds.filter { it.isNotEmpty() },
Namespace eq query.namespace,
if (query.playerId != null || query.displayTests) null else DialogCol_.Test eq false,
if (query.playerId == null) null else PlayerIds.id eq query.playerId!!.id,
if (query.dialogId == null) null else _id eq query.dialogId!!.toId(),
if (from == null) null else DialogCol_.LastUpdateDate gt from?.toInstant(),
if (to == null) null else DialogCol_.LastUpdateDate lt to?.toInstant(),
if (connectorType == null) null else Stories.actions.state.targetConnectorType.id eq connectorType!!.id,
if (query.intentName.isNullOrBlank()) null else Stories.currentIntent.name_ eq query.intentName,
DialogCol_.Rating `in` setOf(1, 2, 3, 4, 5)
)
),
group(
null,
ParseRequestSatisfactionStatCol_.Count sum 1,
ParseRequestSatisfactionStatCol_.Rating avg ParseRequestSatisfactionStatCol_.Rating
),
sort(
ascending(
ParseRequestSatisfactionStatCol_.Rating,
)
)
).map { RatingReportQueryResult(it.rating, it.count, emptyList()) }.first()
val applicationsIds = getApplicationIds(query.namespace, query.nlpModel)
val matchConditions = and(
DialogCol_.ApplicationIds `in` applicationsIds.filter { it.isNotEmpty() },
Namespace eq query.namespace,
Test eq false,
DialogCol_.Rating `in` setOf(1, 2, 3, 4, 5)
)
val dialogRatingGroup = group(
DialogCol_.Rating from DialogCol_.Rating,
ParseRequestSatisfactionStatCol_.Count sum 1,
ParseRequestSatisfactionStatCol_.Rating avg ParseRequestSatisfactionStatCol_.Rating
)
val dialogColAggregation =
dialogCol.aggregate<ParseRequestSatisfactionStatCol>(match(matchConditions), dialogRatingGroup)
val dialogRatings = dialogColAggregation.map { DialogRating(it.rating , it.count) }.toList()
if (dialogRatings.isNotEmpty()){
val nb = dialogRatings.sumByLong { it.nbUsers!!.toLong() }
val avg = dialogRatings.sumOf { it.nbUsers!! * it.rating!! } / nb
return RatingReportQueryResult(avg,nb.toInt(),dialogRatings)
}
return null
}

override fun getDialog(id: Id<Dialog>): DialogReport? {
Expand Down

0 comments on commit a97253f

Please sign in to comment.