Added support for ordering stats

This commit is contained in:
Fabio Manganiello 2025-04-08 02:06:09 +02:00
parent 46e94c4a31
commit d9e891a821
Signed by: blacklight
GPG key ID: D90FBA7F76362774
3 changed files with 101 additions and 15 deletions
frontend/src/views
src

View file

@ -20,12 +20,24 @@
<table class="table" v-if="stats.length">
<thead>
<tr>
<th class="key" v-for="attr in Object.keys(stats[0].key)" :key="attr">
{{ displayName(attr) }}
<th class="key"
@click="onColumnHeadClick(attr)"
v-for="attr in Object.keys(stats[0].key)"
:key="attr">
<font-awesome-icon v-if="orderBy === attr"
:icon="['fas', order === 'asc' ? 'sort-up' : 'sort-down']" />
<font-awesome-icon icon="fas fa-sort" v-else />
&nbsp;{{ displayName(attr) }}
</th>
<th :class="column.className"
@click="onColumnHeadClick(columnName)"
v-for="column, columnName in columns"
:key="columnName">
<font-awesome-icon v-if="orderBy === columnName"
:icon="['fas', order === 'asc' ? 'sort-up' : 'sort-down']" />
<font-awesome-icon icon="fas fa-sort" v-else />
&nbsp;{{ column.displayName }}
</th>
<th class="count"># of records</th>
<th class="date">First record</th>
<th class="date">Last record</th>
</tr>
</thead>
<tbody>
@ -104,6 +116,20 @@ export default {
data() {
return {
columns: {
count: {
displayName: '# of records',
className: 'count',
},
startDate: {
displayName: 'First record',
className: 'date',
},
endDate: {
displayName: 'Last record',
className: 'date',
},
},
loading: false,
metrics: {
country: true,
@ -112,6 +138,8 @@ export default {
postalCode: false,
description: false,
},
order: 'desc',
orderBy: 'count',
showSelectForm: false,
stats: [] as LocationStats[],
}
@ -122,11 +150,34 @@ export default {
return new StatsRequest({
// @ts-ignore
userId: this.$root.user.id,
order: this.order,
orderBy: this.orderBy,
groupBy: Object.entries(this.metrics)
.filter(([_, enabled]) => enabled)
.map(([metric]) => metric),
})
},
urlQuery() {
return Object.entries(this.query)
.map(([key, value]) => {
if (key === 'userId') {
return ''
}
if (key === 'groupBy') {
return `${key}=${(value as string[]).sort().join(',')}`;
}
if (key === 'order') {
value = (value as string)?.toLowerCase();
}
return `${key}=${encodeURIComponent(value as any)}`
})
.filter((param: string) => param.length)
.join('&');
},
},
methods: {
@ -149,16 +200,14 @@ export default {
return `/#${key}&order=${opts.ascending ? 'asc' : 'desc'}`
},
setURLQuery() {
const enabledMetrics = Object.entries(this.metrics)
.filter(([_, enabled]) => enabled)
.map(([metric]) => metric);
async setURLQuery() {
window.history.replaceState(
window.history.state,
'',
`${window.location.pathname}#groupBy=${enabledMetrics.sort().join(',')}`,
`${window.location.pathname}#${this.urlQuery}`,
);
await this.refresh();
},
setState() {
@ -183,6 +232,9 @@ export default {
}
}
this.orderBy = params.get('orderBy') || 'count';
this.order = params.get('order') || 'desc';
this.metrics = metrics as {
country: boolean,
locality: boolean,
@ -208,6 +260,15 @@ export default {
this.closeForm();
},
onColumnHeadClick(attr: string) {
if (this.orderBy === attr) {
this.order = this.order === 'asc' ? 'desc' : 'asc';
} else {
this.orderBy = attr;
this.order = attr === 'count' ? 'desc' : 'asc';
}
},
displayValue(key: string, value: any) {
if (key === 'country') {
return this.displayCountry(value);
@ -242,16 +303,26 @@ export default {
query: {
handler() {
this.setURLQuery();
this.refresh();
},
deep: true,
},
orderBy: {
handler() {
this.setURLQuery();
},
},
order: {
handler() {
this.setURLQuery();
},
},
},
async created() {
this.setState();
this.setURLQuery();
await this.refresh();
await this.setURLQuery();
},
}
</script>
@ -303,6 +374,18 @@ export default {
}
table {
thead {
th {
margin: 0 auto;
cursor: pointer;
&:hover {
background-color: var(--color-accent);
color: var(--color-background);
}
}
}
tbody {
tr {
td {

View file

@ -24,7 +24,7 @@ class Stats {
.map((d) => d.dataValues.id)
},
group: groupBy,
order: [[Sequelize.fn('COUNT', Sequelize.col($db.locationTableColumns.id)), req.order]],
order: [[req.orderBy, req.order]],
})
).map(({dataValues: data}: any) =>
new LocationStats({

View file

@ -4,11 +4,13 @@ type GroupBy = 'device' | 'country' | 'locality' | 'postalCode' | 'description';
class StatsRequest {
userId: number;
groupBy: GroupBy[];
orderBy: string = 'count';
order: Order = 'DESC';
constructor(req: {
userId: number;
groupBy: string[] | string;
orderBy?: string;
order?: string;
}) {
this.userId = req.userId;
@ -18,6 +20,7 @@ class StatsRequest {
req.groupBy
) as GroupBy[];
this.orderBy = req.orderBy || this.orderBy;
this.order = (req.order || this.order).toUpperCase() as Order;
}
}