Skip to content

Commit 1ee6f72

Browse files
feat: easy filters
Signed-off-by: Henry Gressmann <[email protected]>
1 parent 1e21f4b commit 1ee6f72

File tree

11 files changed

+188
-81
lines changed

11 files changed

+188
-81
lines changed

src/app/core/reports.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::fmt::{Debug, Display};
33

44
use crate::app::DuckDBConn;
55
use crate::utils::duckdb::{repeat_vars, ParamVec};
6-
use crate::web::routes::dashboard::GraphValue;
76
use duckdb::params_from_iter;
87
use eyre::{bail, Result};
98
use poem_openapi::{Enum, Object};
@@ -76,8 +75,8 @@ pub enum FilterType {
7675
IsFalse,
7776
}
7877

79-
pub type ReportGraph = Vec<GraphValue>;
80-
pub type ReportTable = BTreeMap<String, GraphValue>;
78+
pub type ReportGraph = Vec<f64>;
79+
pub type ReportTable = BTreeMap<String, f64>;
8180

8281
#[derive(Object, Clone, Debug, Default)]
8382
#[oai(rename_all = "camelCase")]
@@ -222,7 +221,7 @@ pub fn overall_report(
222221
metric: &Metric,
223222
) -> Result<ReportGraph> {
224223
if entities.is_empty() {
225-
return Ok(vec![GraphValue::U64(0); data_points as usize]);
224+
return Ok(vec![0.; data_points as usize]);
226225
}
227226

228227
let mut params = ParamVec::new();
@@ -293,13 +292,13 @@ pub fn overall_report(
293292

294293
match metric {
295294
Metric::Views | Metric::UniqueVisitors | Metric::Sessions => {
296-
let rows = stmt.query_map(duckdb::params_from_iter(params), |row| Ok(GraphValue::U64(row.get(1)?)))?;
297-
let report_graph = rows.collect::<Result<Vec<GraphValue>, duckdb::Error>>()?;
295+
let rows = stmt.query_map(duckdb::params_from_iter(params), |row| Ok(row.get(1)?))?;
296+
let report_graph = rows.collect::<Result<Vec<f64>, duckdb::Error>>()?;
298297
Ok(report_graph)
299298
}
300299
Metric::AvgViewsPerSession => {
301-
let rows = stmt.query_map(duckdb::params_from_iter(params), |row| Ok(GraphValue::F64(row.get(1)?)))?;
302-
let report_graph = rows.collect::<Result<Vec<GraphValue>, duckdb::Error>>()?;
300+
let rows = stmt.query_map(duckdb::params_from_iter(params), |row| Ok(row.get(1)?))?;
301+
let report_graph = rows.collect::<Result<Vec<f64>, duckdb::Error>>()?;
303302
Ok(report_graph)
304303
}
305304
}
@@ -447,19 +446,17 @@ pub fn dimension_report(
447446
Metric::Views | Metric::UniqueVisitors | Metric::Sessions => {
448447
let rows = stmt.query_map(params_from_iter(params), |row| {
449448
let dimension_value: String = row.get(0)?;
450-
let total_metric: u64 = row.get(1)?;
451-
Ok((dimension_value, GraphValue::U64(total_metric)))
449+
Ok((dimension_value, row.get(1)?))
452450
})?;
453-
let report_table = rows.collect::<Result<BTreeMap<String, GraphValue>, duckdb::Error>>()?;
451+
let report_table = rows.collect::<Result<BTreeMap<String, f64>, duckdb::Error>>()?;
454452
Ok(report_table)
455453
}
456454
Metric::AvgViewsPerSession => {
457455
let rows = stmt.query_map(params_from_iter(params), |row| {
458456
let dimension_value: String = row.get(0)?;
459-
let total_metric: f64 = row.get(1)?;
460-
Ok((dimension_value, GraphValue::F64(total_metric)))
457+
Ok((dimension_value, row.get(1)?))
461458
})?;
462-
let report_table = rows.collect::<Result<BTreeMap<String, GraphValue>, duckdb::Error>>()?;
459+
let report_table = rows.collect::<Result<BTreeMap<String, f64>, duckdb::Error>>()?;
463460
Ok(report_table)
464461
}
465462
}

src/web/routes/dashboard.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,11 @@ use poem::http::StatusCode;
88
use poem::web::Data;
99
use poem_openapi::param::Path;
1010
use poem_openapi::payload::Json;
11-
use poem_openapi::{Object, OpenApi, Union};
12-
13-
#[derive(Union, Debug, PartialEq, Clone, Copy)]
14-
pub enum GraphValue {
15-
U64(u64),
16-
F64(f64),
17-
}
11+
use poem_openapi::{Object, OpenApi};
1812

1913
#[derive(Object)]
2014
struct GraphResponse {
21-
data: Vec<GraphValue>,
15+
data: Vec<f64>,
2216
}
2317

2418
#[derive(Object)]
@@ -63,7 +57,7 @@ struct DimensionResponse {
6357
#[oai(rename_all = "camelCase")]
6458
struct DimensionTableRow {
6559
dimension_value: String,
66-
value: GraphValue,
60+
value: f64,
6761
display_name: Option<String>,
6862
icon: Option<String>,
6963
}

web/bun.lockb

0 Bytes
Binary file not shown.

web/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"@scaleway/use-query-params": "^5.0.6",
2222
"@tanstack/react-query": "^5.56.2",
2323
"@uidotdev/usehooks": "^2.4.1",
24-
"date-fns": "^3.6.0",
24+
"date-fns": "^4.1.0",
2525
"fets": "^0.8.3",
2626
"fuzzysort": "^3.0.2",
2727
"lightningcss": "^1.27.0",
@@ -34,7 +34,7 @@
3434
},
3535
"devDependencies": {
3636
"@biomejs/biome": "1.9.1",
37-
"@types/react": "^18.3.5",
37+
"@types/react": "^18.3.7",
3838
"@types/react-dom": "^18.3.0",
3939
"@types/react-simple-maps": "^3.0.6",
4040
"astro": "^4.15.6",

web/src/api/dashboard.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

web/src/components/dimensions/dimensions.module.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,26 @@
118118
align-items: center;
119119
}
120120

121+
.hostname {
122+
opacity: 0.7;
123+
font-size: 0.6rem;
124+
}
125+
126+
.dimensionItemSelect {
127+
all: unset;
128+
position: relative;
129+
color: var(--pico-h2-color);
130+
131+
&:hover {
132+
text-decoration: underline;
133+
text-decoration-color: var(--pico-h4-color);
134+
text-decoration-style: dotted;
135+
text-decoration-thickness: 0.1rem;
136+
}
137+
138+
cursor: pointer;
139+
}
140+
121141
.dimensionEmpty {
122142
flex: 1;
123143
display: flex;

web/src/components/dimensions/index.tsx

Lines changed: 85 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,56 @@ import {
1414
} from "../../api";
1515

1616
import { BrowserIcon, MobileDeviceIcon, OSIcon, ReferrerIcon } from "../icons";
17-
import { countryCodeToFlag, formatFullUrl, formatHost, getHref, tryParseUrl } from "../../utils";
17+
import { countryCodeToFlag, formatFullUrl, formatHost, formatPath, getHref, tryParseUrl } from "../../utils";
1818
import { DetailsModal } from "./modal";
1919
import { formatMetricVal } from "../../utils";
2020
import type { ProjectQuery } from "../project";
2121

2222
export const cardStyles = styles.card;
2323

24-
export const DimensionCard = ({
25-
dimension,
26-
query,
27-
}: {
24+
type DimensionProps = {
2825
dimension: Dimension;
2926
query: ProjectQuery;
30-
}) => {
27+
onSelect: (value: DimensionTableRow) => void;
28+
};
29+
30+
export const DimensionCard = (props: DimensionProps) => {
3131
return (
3232
<article className={styles.card}>
3333
<div className={styles.dimensionHeader}>
34-
<div>{dimensionNames[dimension]}</div>
35-
<div>{metricNames[query.metric]}</div>
34+
<div>{dimensionNames[props.dimension]}</div>
35+
<div>{metricNames[props.query.metric]}</div>
3636
</div>
37-
<DimensionTable dimension={dimension} query={query} />
37+
<DimensionTable {...props} />
3838
</article>
3939
);
4040
};
4141

42-
export const DimensionTabsCard = ({ dimensions, query }: { dimensions: Dimension[]; query: ProjectQuery }) => {
42+
export const DimensionTabsCard = ({
43+
dimensions,
44+
query,
45+
onSelect,
46+
}: {
47+
dimensions: Dimension[];
48+
query: ProjectQuery;
49+
onSelect: (value: DimensionTableRow, dimension: Dimension) => void;
50+
}) => {
4351
return (
4452
<article className={styles.card}>
45-
<DimensionTabs dimensions={dimensions} query={query} />
53+
<DimensionTabs dimensions={dimensions} query={query} onSelect={onSelect} />
4654
</article>
4755
);
4856
};
4957

50-
export const DimensionTabs = ({ dimensions, query }: { dimensions: Dimension[]; query: ProjectQuery }) => {
58+
export const DimensionTabs = ({
59+
dimensions,
60+
query,
61+
onSelect,
62+
}: {
63+
dimensions: Dimension[];
64+
query: ProjectQuery;
65+
onSelect: (value: DimensionTableRow, dimension: Dimension) => void;
66+
}) => {
5167
return (
5268
<Tabs.Root className={styles.tabs} defaultValue={dimensions[0]}>
5369
<Tabs.List className={styles.tabsList}>
@@ -60,18 +76,15 @@ export const DimensionTabs = ({ dimensions, query }: { dimensions: Dimension[];
6076
</Tabs.List>
6177
{dimensions.map((dimension) => (
6278
<Tabs.Content key={dimension} value={dimension} className={styles.tabsContent}>
63-
<DimensionTable dimension={dimension} noHeader query={query} />
79+
<DimensionTable dimension={dimension} query={query} onSelect={(value) => onSelect(value, dimension)} />
6480
</Tabs.Content>
6581
))}
6682
</Tabs.Root>
6783
);
6884
};
6985

70-
export const DimensionTable = ({
71-
dimension,
72-
query,
73-
}: { dimension: Dimension; noHeader?: boolean; query: ProjectQuery }) => {
74-
const { data, biggest, order, isLoading } = useDimension({ dimension, ...query });
86+
export const DimensionTable = (props: DimensionProps) => {
87+
const { data, biggest, order, isLoading } = useDimension({ dimension: props.dimension, ...props.query });
7588

7689
const dataTruncated = data?.slice(0, 6);
7790
return (
@@ -85,7 +98,7 @@ export const DimensionTable = ({
8598
className={styles.dimensionRow}
8699
>
87100
<DimensionValueBar value={d.value} biggest={biggest}>
88-
<DimensionLabel dimension={dimension} value={d} />
101+
<DimensionLabel dimension={props.dimension} value={d} onSelect={props.onSelect} />
89102
</DimensionValueBar>
90103
<div>{formatMetricVal(d.value)}</div>
91104
</div>
@@ -99,77 +112,103 @@ export const DimensionTable = ({
99112
</div>
100113
)}
101114
</div>
102-
<DetailsModal dimension={dimension} query={query} />
115+
<DetailsModal {...props} />
103116
</>
104117
);
105118
};
106119

107-
const dimensionLabels: Record<Dimension, (value: DimensionTableRow) => React.ReactNode> = {
108-
platform: (value) => (
120+
const DimensionValueButton = ({
121+
children,
122+
onSelect,
123+
}: {
124+
children: React.ReactNode;
125+
onSelect: () => void;
126+
}) => (
127+
<button type="button" className={styles.dimensionItemSelect} onClick={onSelect}>
128+
{children}
129+
</button>
130+
);
131+
132+
const dimensionLabels: Record<Dimension, (value: DimensionTableRow, onSelect: () => void) => React.ReactNode> = {
133+
platform: (value, onSelect) => (
109134
<>
110135
<OSIcon os={value.dimensionValue} size={24} />
111-
{value.dimensionValue}
136+
<DimensionValueButton onSelect={onSelect}>{value.dimensionValue}</DimensionValueButton>
112137
</>
113138
),
114-
browser: (value) => (
139+
browser: (value, onSelect) => (
115140
<>
116141
<BrowserIcon browser={value.dimensionValue} size={24} />
117-
{value.dimensionValue}
142+
<DimensionValueButton onSelect={onSelect}>{value.dimensionValue}</DimensionValueButton>
118143
</>
119144
),
120-
url: (value) => {
145+
url: (value, onSelect) => {
121146
const url = tryParseUrl(value.dimensionValue);
122147

123148
return (
124149
<>
125150
<LinkIcon size={16} />
126-
<a target="_blank" rel="noreferrer" href={getHref(url)}>
127-
{formatFullUrl(url)}
151+
<DimensionValueButton onSelect={onSelect}>{formatPath(url)}</DimensionValueButton>
152+
<a href={getHref(url)} target="_blank" rel="noreferrer" className={styles.external}>
153+
<SquareArrowOutUpRightIcon size={16} />
128154
</a>
155+
{typeof url !== "string" && <span className={styles.hostname}>{formatHost(url)}</span>}
129156
</>
130157
);
131158
},
132-
fqdn: (value) => {
159+
fqdn: (value, onSelect) => {
133160
const url = tryParseUrl(value.dimensionValue);
134161
return (
135162
<>
136163
<LinkIcon size={16} />
137-
<a target="_blank" rel="noreferrer" href={getHref(url)}>
138-
{formatHost(url)}
164+
<DimensionValueButton onSelect={onSelect}>{formatHost(url)}</DimensionValueButton>
165+
<a href={getHref(url)} target="_blank" rel="noreferrer" className={styles.external}>
166+
<SquareArrowOutUpRightIcon size={16} />
139167
</a>
140168
</>
141169
);
142170
},
143-
mobile: (value) => (
171+
mobile: (value, onSelect) => (
144172
<>
145173
<MobileDeviceIcon isMobile={value.dimensionValue === "true"} size={24} />
146-
{value.dimensionValue === "true" ? "Mobile" : "Desktop"}
174+
<DimensionValueButton onSelect={onSelect}>
175+
{value.dimensionValue === "true" ? "Mobile" : "Desktop"}
176+
</DimensionValueButton>
147177
</>
148178
),
149-
country: (value) => (
179+
country: (value, onSelect) => (
150180
<>
151181
<span>{countryCodeToFlag(value.dimensionValue)}</span>
152-
{value.displayName || value.dimensionValue || "Unknown"}
182+
<DimensionValueButton onSelect={onSelect}>
183+
{value.displayName || value.dimensionValue || "Unknown"}
184+
</DimensionValueButton>
153185
</>
154186
),
155-
city: (value) => (
187+
city: (value, onSelect) => (
156188
<>
157189
<span>{countryCodeToFlag(value.icon || "XX")}</span>
158-
{value.displayName || "Unknown"}
190+
<DimensionValueButton onSelect={onSelect}>{value.displayName || "Unknown"}</DimensionValueButton>
159191
</>
160192
),
161-
referrer: (value) => (
193+
referrer: (value, onSelect) => (
162194
<>
163195
<ReferrerIcon referrer={value.dimensionValue} icon={value.icon} size={24} />
164-
{value.displayName || value.dimensionValue || "Unknown"}
196+
<DimensionValueButton onSelect={onSelect}>
197+
{value.displayName || value.dimensionValue || "Unknown"}
198+
</DimensionValueButton>
165199
{value.dimensionValue && isValidFqdn(value.dimensionValue) && (
166200
<a href={`https://${value.dimensionValue}`} target="_blank" rel="noreferrer" className={styles.external}>
167201
<SquareArrowOutUpRightIcon size={16} />
168202
</a>
169203
)}
170204
</>
171205
),
172-
path: (value) => value.dimensionValue,
206+
path: (value, onSelect) => (
207+
<>
208+
<LinkIcon size={16} />
209+
<DimensionValueButton onSelect={onSelect}>{value.dimensionValue}</DimensionValueButton>
210+
</>
211+
),
173212
};
174213

175214
const isValidFqdn = (fqdn: string) => {
@@ -182,8 +221,12 @@ const isValidFqdn = (fqdn: string) => {
182221
}
183222
};
184223

185-
export const DimensionLabel = ({ dimension, value }: { dimension: Dimension; value: DimensionTableRow }) =>
186-
dimensionLabels[dimension](value);
224+
export const DimensionLabel = ({
225+
dimension,
226+
value,
227+
onSelect,
228+
}: { dimension: Dimension; value: DimensionTableRow; onSelect: (value: DimensionTableRow) => void }) =>
229+
dimensionLabels[dimension](value, () => onSelect(value));
187230

188231
export const DimensionValueBar = ({
189232
value,

web/src/components/graph/graph.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ export const LineGraph = ({
8181
pointLabelYOffset={-12}
8282
enableSlices="x"
8383
sliceTooltip={(props) => <Tooltip {...props} title={title} range={range} />}
84-
enableTouchCrosshair={true}
8584
defs={[
8685
{
8786
colors: [

0 commit comments

Comments
 (0)