Skip to content

Commit

Permalink
feat: easy filters
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
  • Loading branch information
explodingcamera committed Sep 17, 2024
1 parent 1e21f4b commit 1ee6f72
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 81 deletions.
25 changes: 11 additions & 14 deletions src/app/core/reports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::fmt::{Debug, Display};

use crate::app::DuckDBConn;
use crate::utils::duckdb::{repeat_vars, ParamVec};
use crate::web::routes::dashboard::GraphValue;
use duckdb::params_from_iter;
use eyre::{bail, Result};
use poem_openapi::{Enum, Object};
Expand Down Expand Up @@ -76,8 +75,8 @@ pub enum FilterType {
IsFalse,
}

pub type ReportGraph = Vec<GraphValue>;
pub type ReportTable = BTreeMap<String, GraphValue>;
pub type ReportGraph = Vec<f64>;
pub type ReportTable = BTreeMap<String, f64>;

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

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

match metric {
Metric::Views | Metric::UniqueVisitors | Metric::Sessions => {
let rows = stmt.query_map(duckdb::params_from_iter(params), |row| Ok(GraphValue::U64(row.get(1)?)))?;
let report_graph = rows.collect::<Result<Vec<GraphValue>, duckdb::Error>>()?;
let rows = stmt.query_map(duckdb::params_from_iter(params), |row| Ok(row.get(1)?))?;

Check warning on line 295 in src/app/core/reports.rs

View workflow job for this annotation

GitHub Actions / Run tests on ubuntu-latest

question mark operator is useless here

Check warning on line 295 in src/app/core/reports.rs

View workflow job for this annotation

GitHub Actions / Run tests on macos-latest

question mark operator is useless here

Check warning on line 295 in src/app/core/reports.rs

View workflow job for this annotation

GitHub Actions / Run tests on ubuntu-latest

question mark operator is useless here

Check warning on line 295 in src/app/core/reports.rs

View workflow job for this annotation

GitHub Actions / Run tests on macos-latest

question mark operator is useless here
let report_graph = rows.collect::<Result<Vec<f64>, duckdb::Error>>()?;
Ok(report_graph)
}
Metric::AvgViewsPerSession => {
let rows = stmt.query_map(duckdb::params_from_iter(params), |row| Ok(GraphValue::F64(row.get(1)?)))?;
let report_graph = rows.collect::<Result<Vec<GraphValue>, duckdb::Error>>()?;
let rows = stmt.query_map(duckdb::params_from_iter(params), |row| Ok(row.get(1)?))?;

Check warning on line 300 in src/app/core/reports.rs

View workflow job for this annotation

GitHub Actions / Run tests on ubuntu-latest

question mark operator is useless here

Check warning on line 300 in src/app/core/reports.rs

View workflow job for this annotation

GitHub Actions / Run tests on macos-latest

question mark operator is useless here

Check warning on line 300 in src/app/core/reports.rs

View workflow job for this annotation

GitHub Actions / Run tests on ubuntu-latest

question mark operator is useless here

Check warning on line 300 in src/app/core/reports.rs

View workflow job for this annotation

GitHub Actions / Run tests on macos-latest

question mark operator is useless here
let report_graph = rows.collect::<Result<Vec<f64>, duckdb::Error>>()?;
Ok(report_graph)
}
}
Expand Down Expand Up @@ -447,19 +446,17 @@ pub fn dimension_report(
Metric::Views | Metric::UniqueVisitors | Metric::Sessions => {
let rows = stmt.query_map(params_from_iter(params), |row| {
let dimension_value: String = row.get(0)?;
let total_metric: u64 = row.get(1)?;
Ok((dimension_value, GraphValue::U64(total_metric)))
Ok((dimension_value, row.get(1)?))
})?;
let report_table = rows.collect::<Result<BTreeMap<String, GraphValue>, duckdb::Error>>()?;
let report_table = rows.collect::<Result<BTreeMap<String, f64>, duckdb::Error>>()?;
Ok(report_table)
}
Metric::AvgViewsPerSession => {
let rows = stmt.query_map(params_from_iter(params), |row| {
let dimension_value: String = row.get(0)?;
let total_metric: f64 = row.get(1)?;
Ok((dimension_value, GraphValue::F64(total_metric)))
Ok((dimension_value, row.get(1)?))
})?;
let report_table = rows.collect::<Result<BTreeMap<String, GraphValue>, duckdb::Error>>()?;
let report_table = rows.collect::<Result<BTreeMap<String, f64>, duckdb::Error>>()?;
Ok(report_table)
}
}
Expand Down
12 changes: 3 additions & 9 deletions src/web/routes/dashboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,11 @@ use poem::http::StatusCode;
use poem::web::Data;
use poem_openapi::param::Path;
use poem_openapi::payload::Json;
use poem_openapi::{Object, OpenApi, Union};

#[derive(Union, Debug, PartialEq, Clone, Copy)]
pub enum GraphValue {
U64(u64),
F64(f64),
}
use poem_openapi::{Object, OpenApi};

#[derive(Object)]
struct GraphResponse {
data: Vec<GraphValue>,
data: Vec<f64>,
}

#[derive(Object)]
Expand Down Expand Up @@ -63,7 +57,7 @@ struct DimensionResponse {
#[oai(rename_all = "camelCase")]
struct DimensionTableRow {
dimension_value: String,
value: GraphValue,
value: f64,
display_name: Option<String>,
icon: Option<String>,
}
Expand Down
Binary file modified web/bun.lockb
Binary file not shown.
4 changes: 2 additions & 2 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@scaleway/use-query-params": "^5.0.6",
"@tanstack/react-query": "^5.56.2",
"@uidotdev/usehooks": "^2.4.1",
"date-fns": "^3.6.0",
"date-fns": "^4.1.0",
"fets": "^0.8.3",
"fuzzysort": "^3.0.2",
"lightningcss": "^1.27.0",
Expand All @@ -34,7 +34,7 @@
},
"devDependencies": {
"@biomejs/biome": "1.9.1",
"@types/react": "^18.3.5",
"@types/react": "^18.3.7",
"@types/react-dom": "^18.3.0",
"@types/react-simple-maps": "^3.0.6",
"astro": "^4.15.6",
Expand Down
2 changes: 1 addition & 1 deletion web/src/api/dashboard.ts

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions web/src/components/dimensions/dimensions.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,26 @@
align-items: center;
}

.hostname {
opacity: 0.7;
font-size: 0.6rem;
}

.dimensionItemSelect {
all: unset;
position: relative;
color: var(--pico-h2-color);

&:hover {
text-decoration: underline;
text-decoration-color: var(--pico-h4-color);
text-decoration-style: dotted;
text-decoration-thickness: 0.1rem;
}

cursor: pointer;
}

.dimensionEmpty {
flex: 1;
display: flex;
Expand Down
127 changes: 85 additions & 42 deletions web/src/components/dimensions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,56 @@ import {
} from "../../api";

import { BrowserIcon, MobileDeviceIcon, OSIcon, ReferrerIcon } from "../icons";
import { countryCodeToFlag, formatFullUrl, formatHost, getHref, tryParseUrl } from "../../utils";
import { countryCodeToFlag, formatFullUrl, formatHost, formatPath, getHref, tryParseUrl } from "../../utils";
import { DetailsModal } from "./modal";
import { formatMetricVal } from "../../utils";
import type { ProjectQuery } from "../project";

export const cardStyles = styles.card;

export const DimensionCard = ({
dimension,
query,
}: {
type DimensionProps = {
dimension: Dimension;
query: ProjectQuery;
}) => {
onSelect: (value: DimensionTableRow) => void;
};

export const DimensionCard = (props: DimensionProps) => {
return (
<article className={styles.card}>
<div className={styles.dimensionHeader}>
<div>{dimensionNames[dimension]}</div>
<div>{metricNames[query.metric]}</div>
<div>{dimensionNames[props.dimension]}</div>
<div>{metricNames[props.query.metric]}</div>
</div>
<DimensionTable dimension={dimension} query={query} />
<DimensionTable {...props} />
</article>
);
};

export const DimensionTabsCard = ({ dimensions, query }: { dimensions: Dimension[]; query: ProjectQuery }) => {
export const DimensionTabsCard = ({
dimensions,
query,
onSelect,
}: {
dimensions: Dimension[];
query: ProjectQuery;
onSelect: (value: DimensionTableRow, dimension: Dimension) => void;
}) => {
return (
<article className={styles.card}>
<DimensionTabs dimensions={dimensions} query={query} />
<DimensionTabs dimensions={dimensions} query={query} onSelect={onSelect} />
</article>
);
};

export const DimensionTabs = ({ dimensions, query }: { dimensions: Dimension[]; query: ProjectQuery }) => {
export const DimensionTabs = ({
dimensions,
query,
onSelect,
}: {
dimensions: Dimension[];
query: ProjectQuery;
onSelect: (value: DimensionTableRow, dimension: Dimension) => void;
}) => {
return (
<Tabs.Root className={styles.tabs} defaultValue={dimensions[0]}>
<Tabs.List className={styles.tabsList}>
Expand All @@ -60,18 +76,15 @@ export const DimensionTabs = ({ dimensions, query }: { dimensions: Dimension[];
</Tabs.List>
{dimensions.map((dimension) => (
<Tabs.Content key={dimension} value={dimension} className={styles.tabsContent}>
<DimensionTable dimension={dimension} noHeader query={query} />
<DimensionTable dimension={dimension} query={query} onSelect={(value) => onSelect(value, dimension)} />
</Tabs.Content>
))}
</Tabs.Root>
);
};

export const DimensionTable = ({
dimension,
query,
}: { dimension: Dimension; noHeader?: boolean; query: ProjectQuery }) => {
const { data, biggest, order, isLoading } = useDimension({ dimension, ...query });
export const DimensionTable = (props: DimensionProps) => {
const { data, biggest, order, isLoading } = useDimension({ dimension: props.dimension, ...props.query });

const dataTruncated = data?.slice(0, 6);
return (
Expand All @@ -85,7 +98,7 @@ export const DimensionTable = ({
className={styles.dimensionRow}
>
<DimensionValueBar value={d.value} biggest={biggest}>
<DimensionLabel dimension={dimension} value={d} />
<DimensionLabel dimension={props.dimension} value={d} onSelect={props.onSelect} />
</DimensionValueBar>
<div>{formatMetricVal(d.value)}</div>
</div>
Expand All @@ -99,77 +112,103 @@ export const DimensionTable = ({
</div>
)}
</div>
<DetailsModal dimension={dimension} query={query} />
<DetailsModal {...props} />
</>
);
};

const dimensionLabels: Record<Dimension, (value: DimensionTableRow) => React.ReactNode> = {
platform: (value) => (
const DimensionValueButton = ({
children,
onSelect,
}: {
children: React.ReactNode;
onSelect: () => void;
}) => (
<button type="button" className={styles.dimensionItemSelect} onClick={onSelect}>
{children}
</button>
);

const dimensionLabels: Record<Dimension, (value: DimensionTableRow, onSelect: () => void) => React.ReactNode> = {
platform: (value, onSelect) => (
<>
<OSIcon os={value.dimensionValue} size={24} />
{value.dimensionValue}
<DimensionValueButton onSelect={onSelect}>{value.dimensionValue}</DimensionValueButton>
</>
),
browser: (value) => (
browser: (value, onSelect) => (
<>
<BrowserIcon browser={value.dimensionValue} size={24} />
{value.dimensionValue}
<DimensionValueButton onSelect={onSelect}>{value.dimensionValue}</DimensionValueButton>
</>
),
url: (value) => {
url: (value, onSelect) => {
const url = tryParseUrl(value.dimensionValue);

return (
<>
<LinkIcon size={16} />
<a target="_blank" rel="noreferrer" href={getHref(url)}>
{formatFullUrl(url)}
<DimensionValueButton onSelect={onSelect}>{formatPath(url)}</DimensionValueButton>
<a href={getHref(url)} target="_blank" rel="noreferrer" className={styles.external}>
<SquareArrowOutUpRightIcon size={16} />
</a>
{typeof url !== "string" && <span className={styles.hostname}>{formatHost(url)}</span>}
</>
);
},
fqdn: (value) => {
fqdn: (value, onSelect) => {
const url = tryParseUrl(value.dimensionValue);
return (
<>
<LinkIcon size={16} />
<a target="_blank" rel="noreferrer" href={getHref(url)}>
{formatHost(url)}
<DimensionValueButton onSelect={onSelect}>{formatHost(url)}</DimensionValueButton>
<a href={getHref(url)} target="_blank" rel="noreferrer" className={styles.external}>
<SquareArrowOutUpRightIcon size={16} />
</a>
</>
);
},
mobile: (value) => (
mobile: (value, onSelect) => (
<>
<MobileDeviceIcon isMobile={value.dimensionValue === "true"} size={24} />
{value.dimensionValue === "true" ? "Mobile" : "Desktop"}
<DimensionValueButton onSelect={onSelect}>
{value.dimensionValue === "true" ? "Mobile" : "Desktop"}
</DimensionValueButton>
</>
),
country: (value) => (
country: (value, onSelect) => (
<>
<span>{countryCodeToFlag(value.dimensionValue)}</span>
{value.displayName || value.dimensionValue || "Unknown"}
<DimensionValueButton onSelect={onSelect}>
{value.displayName || value.dimensionValue || "Unknown"}
</DimensionValueButton>
</>
),
city: (value) => (
city: (value, onSelect) => (
<>
<span>{countryCodeToFlag(value.icon || "XX")}</span>
{value.displayName || "Unknown"}
<DimensionValueButton onSelect={onSelect}>{value.displayName || "Unknown"}</DimensionValueButton>
</>
),
referrer: (value) => (
referrer: (value, onSelect) => (
<>
<ReferrerIcon referrer={value.dimensionValue} icon={value.icon} size={24} />
{value.displayName || value.dimensionValue || "Unknown"}
<DimensionValueButton onSelect={onSelect}>
{value.displayName || value.dimensionValue || "Unknown"}
</DimensionValueButton>
{value.dimensionValue && isValidFqdn(value.dimensionValue) && (
<a href={`https://${value.dimensionValue}`} target="_blank" rel="noreferrer" className={styles.external}>
<SquareArrowOutUpRightIcon size={16} />
</a>
)}
</>
),
path: (value) => value.dimensionValue,
path: (value, onSelect) => (
<>
<LinkIcon size={16} />
<DimensionValueButton onSelect={onSelect}>{value.dimensionValue}</DimensionValueButton>
</>
),
};

const isValidFqdn = (fqdn: string) => {
Expand All @@ -182,8 +221,12 @@ const isValidFqdn = (fqdn: string) => {
}
};

export const DimensionLabel = ({ dimension, value }: { dimension: Dimension; value: DimensionTableRow }) =>
dimensionLabels[dimension](value);
export const DimensionLabel = ({
dimension,
value,
onSelect,
}: { dimension: Dimension; value: DimensionTableRow; onSelect: (value: DimensionTableRow) => void }) =>
dimensionLabels[dimension](value, () => onSelect(value));

export const DimensionValueBar = ({
value,
Expand Down
1 change: 0 additions & 1 deletion web/src/components/graph/graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ export const LineGraph = ({
pointLabelYOffset={-12}
enableSlices="x"
sliceTooltip={(props) => <Tooltip {...props} title={title} range={range} />}
enableTouchCrosshair={true}
defs={[
{
colors: [
Expand Down
Loading

0 comments on commit 1ee6f72

Please sign in to comment.