@@ -22,9 +22,11 @@ import com.squareup.invert.models.ModulePath
2222import com.squareup.invert.models.OwnerName
2323import com.squareup.invert.models.StatKey
2424import com.squareup.invert.models.StatMetadata
25+ import org.jetbrains.compose.web.css.CSSUnit
2526import org.jetbrains.compose.web.dom.H1
2627import org.jetbrains.compose.web.dom.H3
2728import org.jetbrains.compose.web.dom.H4
29+ import org.jetbrains.compose.web.dom.H5
2830import org.jetbrains.compose.web.dom.Li
2931import org.jetbrains.compose.web.dom.P
3032import org.jetbrains.compose.web.dom.Text
@@ -46,6 +48,7 @@ data class CodeReferencesNavRoute(
4648 val module : String? = null ,
4749 val treemap : Boolean? = null ,
4850 val chart : Boolean? = null ,
51+ val extras : Map <String , String >? = null
4952) : BaseNavRoute(CodeReferencesReportPage .navPage) {
5053
5154 override fun toSearchParams (): Map <String , String > = toParamsWithOnlyPageId(this )
@@ -65,6 +68,11 @@ data class CodeReferencesNavRoute(
6568 chart?.let {
6669 params[CHART_PARAM ] = chart.toString()
6770 }
71+ extras?.let {
72+ it.forEach { (key, value) ->
73+ params[" extra_$key " ] = value
74+ }
75+ }
6876 }
6977
7078 companion object {
@@ -74,37 +82,51 @@ data class CodeReferencesNavRoute(
7482 private const val OWNER_PARAM = " owner"
7583 private const val MODULE_PARAM = " module"
7684 private const val TREEMAP_PARAM = " treemap"
85+ private const val EXTRAS_PARAM_PREFIX = " extra_"
86+
7787
7888 fun parser (params : Map <String , String ?>): NavRoute {
79- val statKey = params[STATKEY_PARAM ]
80- val owner = params[OWNER_PARAM ]?.trim()?.let {
81- if (it.isNotBlank()) {
82- it
83- } else {
84- null
85- }
86- }
87- val module = params[MODULE_PARAM ]?.trim()?.let {
88- if (it.isNotBlank()) {
89- it
90- } else {
91- null
92- }
93- }
94- val treemap = params[TREEMAP_PARAM ]?.trim()?.let {
95- if (it.isNotBlank()) {
96- it.toBoolean()
97- } else {
98- null
89+ var statKey: String? = null
90+ var owner: String? = null
91+ var module: String? = null
92+ var treemap: Boolean? = null
93+ var chart: Boolean? = null
94+ var extras: MutableMap <String , String > = mutableMapOf ()
95+ params.forEach { (key, value) ->
96+ val trimmedValue = value?.trim()?.let {
97+ if (it.isNotBlank()) {
98+ it
99+ } else {
100+ null
101+ }
99102 }
100- }
101- val chart = params[CHART_PARAM ]?.trim()?.let {
102- if (it.isNotBlank()) {
103- it.toBoolean()
104- } else {
105- null
103+ if (trimmedValue != null ) {
104+ when (key) {
105+ STATKEY_PARAM -> {
106+ statKey = trimmedValue
107+ }
108+ OWNER_PARAM -> {
109+ owner = trimmedValue
110+ }
111+ MODULE_PARAM -> {
112+ module = trimmedValue
113+ }
114+ TREEMAP_PARAM -> {
115+ treemap = trimmedValue.toBoolean()
116+ }
117+ CHART_PARAM -> {
118+ chart = trimmedValue.toBoolean()
119+ }
120+ else -> {
121+ if (key.startsWith(EXTRAS_PARAM_PREFIX )) {
122+ val extraKey = key.substring(EXTRAS_PARAM_PREFIX .length)
123+ extras[extraKey] = trimmedValue
124+ }
125+ }
126+ }
106127 }
107128 }
129+
108130 return if (statKey == null ) {
109131 AllStatsNavRoute ()
110132 } else {
@@ -114,6 +136,7 @@ data class CodeReferencesNavRoute(
114136 module = module,
115137 treemap = treemap,
116138 chart = chart,
139+ extras = extras.let { if (it.isEmpty()) null else it }
117140 )
118141 }
119142 }
@@ -293,6 +316,18 @@ fun CodeReferencesComposable(
293316 true
294317 }
295318 }
319+ // Filter by extras
320+ .filter { ownerAndCodeReference: ModuleOwnerAndCodeReference ->
321+ val codeReference = ownerAndCodeReference.codeReference
322+ val extras = codeReferencesNavRoute.extras ? : mapOf ()
323+ if (extras.isEmpty()) {
324+ true
325+ } else {
326+ extras.all { (key, value) ->
327+ codeReference.extras[key] == value
328+ }
329+ }
330+ }
296331
297332 if (codeReferencesNavRoute.treemap == true ) {
298333 BootstrapRow {
@@ -337,12 +372,18 @@ fun CodeReferencesComposable(
337372 }
338373 }
339374
375+ BootstrapRow {
376+ H3 {
377+ Text (" Filters" )
378+ }
379+ }
380+
340381 val codeReferencesByOwner = allCodeReferencesForStat.groupBy { it.owner }
341382 val totalCodeReferenceCount = allCodeReferencesForStat.size
342383 BootstrapRow {
343384 BootstrapColumn (6 ) {
344- H3 {
345- Text (" Filter by Owner" )
385+ H5 {
386+ Text (" Owner" )
346387 BootstrapSelectDropdown (
347388 placeholderText = " -- All Owners ($totalCodeReferenceCount Total) --" ,
348389 currentValue = codeReferencesNavRoute.owner,
@@ -364,8 +405,8 @@ fun CodeReferencesComposable(
364405 val codeReferencesByModule =
365406 allCodeReferencesForStat.groupBy { it.module }
366407 BootstrapColumn (6 ) {
367- H3 {
368- Text (" Filter by Module" )
408+ H5 {
409+ Text (" Module" )
369410 BootstrapSelectDropdown (
370411 placeholderText = " -- All Modules --" ,// (${codeReferencesByModule.size} Total) --",
371412 currentValue = codeReferencesNavRoute.module,
@@ -385,6 +426,58 @@ fun CodeReferencesComposable(
385426 }
386427 }
387428 }
429+ // Limit filterable extras to ones where the amount of possible values is within a reasonable limit
430+ val filterableExtraCountLimit = 5000
431+ val allExtraValuesByKey = allCodeReferencesForStat.flatMap {
432+ it.codeReference.extras.entries.toList()
433+ }.groupBy { it.key }.mapValues { it.value.map { entry -> entry.value }.toSet().sorted() }
434+ val filterableExtras = currentStatMetadata.extras.filter {
435+ val extraValues = allExtraValuesByKey[it.key] ? : emptyList()
436+ (it.type == ExtraDataType .STRING || it.type == ExtraDataType .BOOLEAN ) &&
437+ extraValues.size < filterableExtraCountLimit
438+ }.sortedBy { it.description }
439+ if (filterableExtras.isNotEmpty()) {
440+ filterableExtras.chunked(size = 2 ).map { extraGroup ->
441+ BootstrapRow {
442+ extraGroup.forEach { extra ->
443+ BootstrapColumn (6 ) {
444+ H5 {
445+ Text (extra.description)
446+ BootstrapSelectDropdown (
447+ placeholderText = " -- All Values --" ,
448+ currentValue = codeReferencesNavRoute.extras?.get(extra.key),
449+ options = allCodeReferencesForStat.mapNotNull {
450+ it.codeReference.extras[extra.key]
451+ }.toSet().map {
452+ BootstrapSelectOption (
453+ value = it,
454+ displayText = it,
455+ )
456+ }.sortedBy { it.displayText }
457+ ) {
458+ val value = it?.value
459+ var newExtras: Map <String , String >? = null
460+ val currentExtras = codeReferencesNavRoute.extras ? : mapOf ()
461+ if (value != null ) {
462+ newExtras = currentExtras + mapOf (extra.key to value)
463+ } else {
464+ newExtras = currentExtras.minus(extra.key)
465+ if (newExtras.isEmpty()) {
466+ newExtras = null
467+ }
468+ }
469+ navRouteRepo.pushNavRoute(
470+ codeReferencesNavRoute.copy(
471+ extras = newExtras
472+ )
473+ )
474+ }
475+ }
476+ }
477+ }
478+ }
479+ }
480+ }
388481
389482 BootstrapTable (
390483 headers = listOf (
0 commit comments