Skip to content

Commit

Permalink
Merge pull request #161 from querqy/usage_heat_bars
Browse files Browse the repository at this point in the history
Usage heat bars (v4.3.0)
  • Loading branch information
pbartusch committed Sep 10, 2024
2 parents 80b524e + cc64081 commit f41117a
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 20 deletions.
2 changes: 1 addition & 1 deletion app/controllers/ApiController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ class ApiController @Inject()(val controllerComponents: SecurityComponents,
def listAll(solrIndexId: String) : Action[AnyContent] = Action {
val searchInputs = searchManagementRepository.listAllSearchInputsInclDirectedSynonyms(SolrIndexId(solrIndexId))
val spellings = searchManagementRepository.listAllSpellingsWithAlternatives(SolrIndexId(solrIndexId))
Ok(Json.toJson(ListItem.create(searchInputs, spellings)))
Ok(Json.toJson(ListItem.create(searchInputs, spellings, rulesUsageService.getRulesUsageStatistics)))
}

def addNewSpelling(solrIndexId: String): Action[AnyContent] = Action.async { request: Request[AnyContent] =>
Expand Down
25 changes: 22 additions & 3 deletions app/models/input/ListItem.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package models.input
import models.input.ListItemType.ListItemType
import models.spellings.{CanonicalSpelling, CanonicalSpellingWithAlternatives}
import play.api.libs.json.{Format, Json, OFormat}
import services.RulesUsage

object ListItemType extends Enumeration {
type ListItemType = Value
Expand All @@ -18,12 +19,30 @@ case class ListItem(id: String,
synonyms: Seq[String] = Seq.empty,
tags: Seq[InputTag] = Seq.empty,
comment: String = "",
additionalTermsForSearch: Seq[String] = Seq.empty)
additionalTermsForSearch: Seq[String] = Seq.empty,
usageFrequency: Option[Int] = None)

object ListItem {
def create(searchInputs: Seq[SearchInputWithRules], spellings: Seq[CanonicalSpellingWithAlternatives]): Seq[ListItem] = {

def create(searchInputs: Seq[SearchInputWithRules],
spellings: Seq[CanonicalSpellingWithAlternatives],
optRuleUsageStatistics: Option[Seq[RulesUsage]]): Seq[ListItem] = {
val listItems = listItemsForRules(searchInputs) ++ listItemsForSpellings(spellings)
listItems.sortBy(_.term.trim.toLowerCase.replace("\"", ""))
// augment with usage statistics, only if available, pass through otherwise
val listItemsWithUsageStatistics = optRuleUsageStatistics match {
case Some(rulesUsage) if rulesUsage.isEmpty => listItems
case Some(ruleUsage) => augmentRulesWithUsage(listItems, ruleUsage)
case None => listItems
}
listItemsWithUsageStatistics.sortBy(_.term.trim.toLowerCase.replace("\"", ""))
}

private def augmentRulesWithUsage(listItems: Seq[ListItem], ruleUsage: Seq[RulesUsage]): Seq[ListItem] = {
// there can be multiple rule usage items for the the same rule, one per keyword combination that triggered the usage
val combinedRuleUsageFrequency = ruleUsage.groupBy(_.inputId.id).view.mapValues(_.map(_.frequency).sum)
listItems.map { listItem =>
listItem.copy(usageFrequency = combinedRuleUsageFrequency.get(listItem.id))
}
}

private def listItemsForRules(searchInputs: Seq[SearchInputWithRules]): Seq[ListItem] = {
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import com.typesafe.sbt.GitBranchPrompt

name := "search-management-ui"
version := "4.2.0"
version := "4.3.0"
maintainer := "Contact productful.io <hello@productful.io>"

scalaVersion := "2.13.14"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,17 @@ li.list-group-item.active > .text-muted {
.smui-align-middle {
vertical-align: middle;
}

.smui-frequency-box-container {
display: inline-block;
width: 32px;
height: 28px;
border-left: 1px solid #666;
}

.smui-frequency-box {
display: inline-block;
height: 1rem;
background-image: linear-gradient(to right, #db5f57, #dbc257, #91db57, #57db80, #57d3db, #5770db, #a157db, #db57b2);
background-size: 35px 16px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,26 @@
}"
(click)="selectListItemWithCheck(listItem)"
>
<div class="smui-frequency-box-container smui-right-gap" *ngIf="shouldDisplayUsageFrequency()">
<span class="smui-frequency-box align-middle"
title="Rule has been applied {{listItem.usageFrequency || '0'}} time(s)."
[style.width.px]="4 * (listItem.usageFrequencyBucket || 0) + 2"
>
</span>
</div>

<span class="smui-right-gap smui-align-middle">
<i
*ngIf="listItem.itemType.toString() === 'RuleManagement'"
class="fa fa-list smui-align-middle"
aria-hidden="true"
></i>
<i
*ngIf="listItem.itemType.toString() === 'Spelling'"
class="fa fa-book smui-align-middle"
aria-hidden="true"
></i>
</span>
<i
*ngIf="listItem.itemType.toString() === 'RuleManagement'"
class="fa fa-list smui-align-middle"
aria-hidden="true"
></i>
<i
*ngIf="listItem.itemType.toString() === 'Spelling'"
class="fa fa-book smui-align-middle"
aria-hidden="true"
></i>
</span>

<span class="smui-align-middle list-item-term">{{ listItem.term }}</span>

Expand Down Expand Up @@ -68,7 +76,7 @@

<div ngbDropdownMenu aria-labelledby="actionsDropdown">
<button ngbDropdownItem
[disabled]="listItem.itemType.toString() !== 'RuleManagement'"
[disabled]="listItem.itemType.toString() !== 'Rule*Management'"
(click)="copyRuleItem(listItem.id, $event)"
>
<i class="fa fa-clone mr-3"></i>Copy to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ export class RulesListComponent implements OnChanges {
this.isShowingAllItems = !this.isShowingAllItems;
}

shouldDisplayUsageFrequency() {
return this.featureToggleService.getSyncToggleRuleUsageStatistics();
}

private selectListItem(listItem?: ListItem) {
console.log(
`In SearchInputListComponent :: selectListItem :: id = ${
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/app/models/list.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ export class ListItem {
tags: Array<InputTag>;
comment: string;
additionalTermsForSearch: Array<string>;
usageFrequency: number | null;
usageFrequencyBucket: number | undefined;
}
28 changes: 25 additions & 3 deletions frontend/src/app/services/list-items.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { map } from 'rxjs/operators';
import { ListItem } from '../models';

@Injectable({
Expand All @@ -14,7 +14,29 @@ export class ListItemsService {

getAllItemsForInputList(solrIndexId: string): Promise<Array<ListItem>> {
return this.http
.get<ListItem[]>(`${this.baseUrl}/${solrIndexId}/${this.listItemsPath}`)
.toPromise();
.get(`${this.baseUrl}/${solrIndexId}/${this.listItemsPath}`)
.pipe(
map((response: any) =>
(response as []).map((item) => Object.assign(new ListItem, item))),
map((listItems: ListItem[]) =>
this.assignRuleUsageBuckets(listItems, 8)
)
).toPromise();
}

assignRuleUsageBuckets(listItems: ListItem[], numBuckets: number): ListItem[] {
if (listItems.length == 0) {
return listItems;
}
// perform a min-max normalization of the log2 of the usage frequency and assign them to $numBuckets buckets
const log2Values = listItems.map(listItem => Math.log2(listItem.usageFrequency || 1));
const minLog2 = Math.min(...log2Values);
const maxLog2 = Math.max(...log2Values);
const originalRange = maxLog2 - minLog2;
return listItems.map(listItem => {
const log2Value = Math.log2(listItem.usageFrequency || 1);
const bucket = Math.round(((log2Value - minLog2) / originalRange) * numBuckets);
return Object.assign(listItem, {"usageFrequencyBucket": bucket})
});
}
}

0 comments on commit f41117a

Please sign in to comment.