Skip to content

Commit 199748c

Browse files
committed
refactor: abstract away report filters
Abstracts away the report filters so they can be reused in other views. Also deletes some unused styles.
1 parent 65bb7ae commit 199748c

File tree

6 files changed

+137
-168
lines changed

6 files changed

+137
-168
lines changed

report-app/src/app/pages/report-list/report-list.html

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,7 @@
33
<message-spinner message="Loading reports"/>
44
} @else {
55
<div class="toolbar">
6-
<div class="filter-container">
7-
@if (allFrameworks().length > 1) {
8-
<select (change)="selectedFramework.set($any($event.target).value)">
9-
<option value="">All Frameworks</option>
10-
@for (framework of allFrameworks(); track framework.id) {
11-
<option [value]="framework.id">{{ framework.displayName }}</option>
12-
}
13-
</select>
14-
}
15-
@if (allModels().length > 1) {
16-
<select (change)="selectedModel.set($any($event.target).value)">
17-
<option value="">All Models</option>
18-
@for (model of allModels(); track model.id) {
19-
<option [value]="model.id">{{ model.displayName }}</option>
20-
}
21-
</select>
22-
}
23-
@if (allRunners().length > 1) {
24-
<select (change)="selectedRunner.set($any($event.target).value)">
25-
<option value="">All Runners</option>
26-
@for (runner of allRunners(); track runner.id) {
27-
<option [value]="runner.id">{{ runner.displayName }}</option>
28-
}
29-
</select>
30-
}
31-
@if (allLabels().length > 0) {
32-
<multi-select
33-
[options]="allLabels()"
34-
[(selected)]="selectedLabels"
35-
[label]="selectedLabels().length === 0 ? 'Filter by labels' : `Showing reports with ${selectedLabels().length} label(s)`"/>
36-
}
37-
</div>
6+
<report-filters #filters/>
387
<div class="button-group">
398
@if (isCompareMode()) {
409
<button class="outlined-button" (click)="toggleCompareMode()">Cancel</button>
@@ -48,7 +17,7 @@
4817
</div>
4918
</div>
5019

51-
@for (group of reportGroups(); track group.id) {
20+
@for (group of filters.filteredGroups(); track group.id) {
5221
<div class="report-item">
5322
<a [routerLink]="['/report', group.id]">
5423
<div class="report-score-container">

report-app/src/app/pages/report-list/report-list.scss

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -93,65 +93,12 @@ h1, h2 {
9393
flex-shrink: 0;
9494
}
9595

96-
.report-item-container {
97-
display: flex;
98-
align-items: center;
99-
gap: 1rem;
100-
}
101-
102-
.report-checkbox {
103-
appearance: none;
104-
-webkit-appearance: none;
105-
margin: 0;
106-
font: inherit;
107-
color: currentColor;
108-
width: 1.5rem;
109-
height: 1.5rem;
110-
border: 0.15rem solid var(--border-color);
111-
border-radius: 0.35rem;
112-
transform: translateY(-0.075em);
113-
display: grid;
114-
place-content: center;
115-
cursor: pointer;
116-
transition: all 0.1s ease-in-out;
117-
}
118-
119-
.report-checkbox::before {
120-
content: '';
121-
width: 0.75rem;
122-
height: 0.75rem;
123-
transform: scale(0);
124-
transition: 120ms transform ease-in-out;
125-
box-shadow: inset 1em 1em var(--card-bg-color);
126-
// Use a CSS trick to create a checkmark
127-
transform-origin: bottom left;
128-
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
129-
}
130-
131-
.report-checkbox:checked::before {
132-
transform: scale(1);
133-
}
134-
135-
.report-checkbox:checked {
136-
background: var(--accent-blue);
137-
border-color: var(--accent-blue);
138-
}
139-
140-
.report-checkbox:hover {
141-
border-color: var(--accent-blue);
142-
}
143-
14496
.toolbar {
14597
display: flex;
14698
justify-content: space-between;
14799
align-items: center;
148100
}
149101

150-
.filter-container {
151-
display: flex;
152-
gap: 1rem;
153-
}
154-
155102
.select-for-comparison {
156103
display: flex;
157104
align-items: center;

report-app/src/app/pages/report-list/report-list.ts

Lines changed: 2 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {MessageSpinner} from '../../shared/message-spinner';
1111
import {Score} from '../../shared/score/score';
1212
import {ProviderLabel} from '../../shared/provider-label';
1313
import {bucketToScoreVariable} from '../../shared/scoring';
14-
import {MultiSelect} from '../../shared/multi-select/multi-select';
14+
import {ReportFilters} from '../../shared/report-filters/report-filters';
1515

1616
@Component({
1717
selector: 'app-report-list',
@@ -22,98 +22,18 @@ import {MultiSelect} from '../../shared/multi-select/multi-select';
2222
MessageSpinner,
2323
Score,
2424
ProviderLabel,
25-
MultiSelect,
25+
ReportFilters,
2626
],
2727
templateUrl: './report-list.html',
2828
styleUrls: ['./report-list.scss'],
2929
})
3030
export class ReportListComponent {
3131
private reportsFetcher = inject(ReportsFetcher);
3232
private router = inject(Router);
33-
private allGroups = this.reportsFetcher.reportGroups;
3433

3534
protected isLoading = this.reportsFetcher.isLoadingReportsList;
3635
protected reportsToCompare = signal<string[]>([]);
3736
protected isServer = isPlatformServer(inject(PLATFORM_ID));
38-
39-
protected selectedFramework = signal<string | null>(null);
40-
protected selectedModel = signal<string | null>(null);
41-
protected selectedRunner = signal<string | null>(null);
42-
protected selectedLabels = signal<string[]>([]);
43-
44-
protected allFrameworks = computed(() => {
45-
const frameworks = new Map<string, string>();
46-
this.allGroups().forEach(group => {
47-
const framework = group.framework.fullStackFramework;
48-
frameworks.set(framework.id, framework.displayName);
49-
});
50-
return Array.from(frameworks.entries()).map(([id, displayName]) => ({
51-
id,
52-
displayName,
53-
}));
54-
});
55-
56-
protected allModels = computed(() => {
57-
const models = new Set(this.allGroups().map(g => g.model));
58-
59-
return Array.from(models).map(model => ({
60-
id: model,
61-
displayName: model,
62-
}));
63-
});
64-
65-
protected allRunners = computed(() => {
66-
const runners = new Map<string, string>();
67-
68-
this.allGroups().forEach(group => {
69-
if (group.runner) {
70-
runners.set(group.runner.id, group.runner.displayName);
71-
}
72-
});
73-
74-
return Array.from(runners.entries()).map(([id, displayName]) => ({
75-
id,
76-
displayName,
77-
}));
78-
});
79-
80-
protected allLabels = computed(() => {
81-
const labels = new Set<string>();
82-
83-
for (const group of this.allGroups()) {
84-
for (const label of group.labels) {
85-
const trimmed = label.trim();
86-
87-
if (trimmed) {
88-
labels.add(trimmed);
89-
}
90-
}
91-
}
92-
93-
return Array.from(labels)
94-
.sort()
95-
.map(label => ({
96-
label,
97-
value: label,
98-
}));
99-
});
100-
101-
protected reportGroups = computed(() => {
102-
const framework = this.selectedFramework();
103-
const model = this.selectedModel();
104-
const runner = this.selectedRunner();
105-
const labels = this.selectedLabels();
106-
const groups = this.allGroups();
107-
108-
return groups.filter(group => {
109-
const frameworkMatch = !framework || group.framework.fullStackFramework.id === framework;
110-
const modelMatch = !model || group.model === model;
111-
const runnerMatch = !runner || group.runner?.id === runner;
112-
const labelsMatch = labels.length === 0 || group.labels.some(l => labels.includes(l.trim()));
113-
return frameworkMatch && modelMatch && runnerMatch && labelsMatch;
114-
});
115-
});
116-
11737
protected isCompareMode = signal(false);
11838

11939
protected handleCompare() {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@if (allFrameworks().length > 1) {
2+
<select (change)="selectedFramework.set($any($event.target).value)">
3+
<option value="">All Frameworks</option>
4+
@for (framework of allFrameworks(); track framework.id) {
5+
<option [value]="framework.id">{{ framework.displayName }}</option>
6+
}
7+
</select>
8+
}
9+
10+
@if (allModels().length > 1) {
11+
<select (change)="selectedModel.set($any($event.target).value)">
12+
<option value="">All Models</option>
13+
@for (model of allModels(); track model.id) {
14+
<option [value]="model.id">{{ model.displayName }}</option>
15+
}
16+
</select>
17+
}
18+
19+
@if (allRunners().length > 1) {
20+
<select (change)="selectedRunner.set($any($event.target).value)">
21+
<option value="">All Runners</option>
22+
@for (runner of allRunners(); track runner.id) {
23+
<option [value]="runner.id">{{ runner.displayName }}</option>
24+
}
25+
</select>
26+
}
27+
28+
@if (allLabels().length > 0) {
29+
<multi-select
30+
[options]="allLabels()"
31+
[(selected)]="selectedLabels"
32+
[label]="selectedLabels().length === 0 ? 'Filter by labels' : `${selectedLabels().length} label(s)`"/>
33+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
:host {
2+
display: flex;
3+
gap: 1rem;
4+
}
5+
6+
select,
7+
multi-select {
8+
max-width: 200px;
9+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import {Component, computed, inject, signal} from '@angular/core';
2+
import {MultiSelect} from '../multi-select/multi-select';
3+
import {ReportsFetcher} from '../../services/reports-fetcher';
4+
5+
/** Renders out a toolbar that filters reports based on the user selection. */
6+
@Component({
7+
selector: 'report-filters',
8+
templateUrl: 'report-filters.html',
9+
styleUrl: 'report-filters.scss',
10+
imports: [MultiSelect],
11+
})
12+
export class ReportFilters {
13+
private reportsFetcher = inject(ReportsFetcher);
14+
protected selectedFramework = signal<string | null>(null);
15+
protected selectedModel = signal<string | null>(null);
16+
protected selectedRunner = signal<string | null>(null);
17+
protected selectedLabels = signal<string[]>([]);
18+
19+
protected allFrameworks = computed(() => {
20+
const frameworks = new Map<string, string>();
21+
this.reportsFetcher.reportGroups().forEach(group => {
22+
const framework = group.framework.fullStackFramework;
23+
frameworks.set(framework.id, framework.displayName);
24+
});
25+
return Array.from(frameworks.entries()).map(([id, displayName]) => ({
26+
id,
27+
displayName,
28+
}));
29+
});
30+
31+
protected allModels = computed(() => {
32+
const models = new Set(this.reportsFetcher.reportGroups().map(g => g.model));
33+
34+
return Array.from(models).map(model => ({
35+
id: model,
36+
displayName: model,
37+
}));
38+
});
39+
40+
protected allRunners = computed(() => {
41+
const runners = new Map<string, string>();
42+
43+
this.reportsFetcher.reportGroups().forEach(group => {
44+
if (group.runner) {
45+
runners.set(group.runner.id, group.runner.displayName);
46+
}
47+
});
48+
49+
return Array.from(runners.entries()).map(([id, displayName]) => ({
50+
id,
51+
displayName,
52+
}));
53+
});
54+
55+
protected allLabels = computed(() => {
56+
const labels = new Set<string>();
57+
58+
for (const group of this.reportsFetcher.reportGroups()) {
59+
for (const label of group.labels) {
60+
const trimmed = label.trim();
61+
62+
if (trimmed) {
63+
labels.add(trimmed);
64+
}
65+
}
66+
}
67+
68+
return Array.from(labels)
69+
.sort()
70+
.map(label => ({
71+
label,
72+
value: label,
73+
}));
74+
});
75+
76+
readonly filteredGroups = computed(() => {
77+
const framework = this.selectedFramework();
78+
const model = this.selectedModel();
79+
const runner = this.selectedRunner();
80+
const labels = this.selectedLabels();
81+
const groups = this.reportsFetcher.reportGroups();
82+
83+
return groups.filter(group => {
84+
const frameworkMatch = !framework || group.framework.fullStackFramework.id === framework;
85+
const modelMatch = !model || group.model === model;
86+
const runnerMatch = !runner || group.runner?.id === runner;
87+
const labelsMatch = labels.length === 0 || group.labels.some(l => labels.includes(l.trim()));
88+
return frameworkMatch && modelMatch && runnerMatch && labelsMatch;
89+
});
90+
});
91+
}

0 commit comments

Comments
 (0)