Skip to content

Commit 5231411

Browse files
chore: improved world map rendering for bounce rate metric
Signed-off-by: Henry <mail@henrygressmann.de>
1 parent 0be71bf commit 5231411

File tree

6 files changed

+36
-12
lines changed

6 files changed

+36
-12
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Since this is not a library, this changelog focuses on the changes that are rele
2121
- Updated to the latest version of DuckDB (1.5.1)
2222
- Fixed error when both `listen` and `port` configuration options are set
2323
- Added retry logic when loading the DuckDB database to handle potential locking issues on startup (e.g. when the database is being updated by another process or when using a shared network drive)
24+
- Improved filtering out invalid referrers
25+
- Improved world map rendering for bounce rate metric
2426

2527
## [v1.4.0] - 2026-03-14
2628

src/utils/referrer.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::str::FromStr;
21
use std::sync::LazyLock;
32

43
use ahash::{HashMap, HashSet};
@@ -46,28 +45,35 @@ pub fn is_spammer(fqdn: &str) -> bool {
4645
pub enum Referrer {
4746
Fqdn(String),
4847
Unknown(Option<String>),
48+
Local,
4949
Spammer,
5050
}
5151

5252
pub fn process_referer(referer: Option<&str>) -> Referrer {
53-
match referer.map(http::Uri::from_str) {
53+
if referer.is_some_and(|referer| {
54+
referer.parse::<std::net::IpAddr>().is_ok() || referer == "localhost" || referer.ends_with(".localhost")
55+
}) {
56+
return Referrer::Local;
57+
}
58+
59+
match referer.map(url::Url::parse) {
5460
// valid referer are stripped to the FQDN
5561
Some(Ok(referer_uri)) => {
5662
// ignore localhost / IP addresses
57-
if referer_uri.host().is_some_and(|host| {
63+
if referer_uri.host_str().is_some_and(|host| {
5864
host == "localhost" || host.ends_with(".localhost") || host.parse::<std::net::IpAddr>().is_ok()
5965
}) {
60-
return Referrer::Unknown(None);
66+
return Referrer::Local;
6167
}
6268

63-
let referer_fqn = referer_uri.host().unwrap_or_default();
69+
let referer_fqn = referer_uri.host_str().unwrap_or_default();
6470
if is_spammer(referer_fqn) {
6571
return Referrer::Spammer;
6672
}
6773
Referrer::Fqdn(referer_fqn.to_string())
6874
}
6975
// invalid referer are kept as is (e.g. when using custom referer values outside of the browser)
70-
Some(Err(_)) => Referrer::Unknown(referer.map(std::string::ToString::to_string)),
76+
Some(Err(_)) => Referrer::Unknown(referer.map(ToString::to_string)),
7177
None => Referrer::Unknown(None),
7278
}
7379
}

src/web/routes/event.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ use axum::Json;
1212
use axum::extract::State;
1313
use axum_extra::TypedHeader;
1414
use chrono::Utc;
15-
use http::{StatusCode, Uri};
15+
use http::StatusCode;
1616
use schemars::JsonSchema;
1717
use std::net::IpAddr;
1818
use std::str::FromStr;
1919
use std::sync::mpsc::Sender;
2020
use std::sync::{Arc, LazyLock};
21+
use url::Url;
2122

2223
pub fn router() -> ApiRouter<RouterState> {
2324
ApiRouter::new().route("/event", post(event_handler))
@@ -50,7 +51,7 @@ async fn event_handler(
5051
TypedHeader(user_agent): TypedHeader<headers::UserAgent>,
5152
Json(event): Json<EventRequest>,
5253
) -> ApiResult<impl IntoApiResponse> {
53-
let url = Uri::from_str(&event.url).context("invalid url").http_err("invalid url", StatusCode::BAD_REQUEST)?;
54+
let url = Url::from_str(&event.url).context("invalid url").http_err("invalid url", StatusCode::BAD_REQUEST)?;
5455
let app = state.app.clone();
5556
let events = state.events.clone();
5657

@@ -68,14 +69,15 @@ fn process_event(
6869
app: Arc<Liwan>,
6970
events: Sender<Event>,
7071
event: EventRequest,
71-
url: Uri,
72+
url: Url,
7273
ip: Option<IpAddr>,
7374
user_agent: headers::UserAgent,
7475
) -> Result<()> {
7576
let referrer = match process_referer(event.referrer.as_deref()) {
7677
Referrer::Fqdn(fqdn) => Some(fqdn),
7778
Referrer::Unknown(r) => r,
7879
Referrer::Spammer => return Ok(()),
80+
Referrer::Local => return Ok(()),
7981
};
8082
let referrer = referrer.map(|r| r.trim_start_matches("www.").to_string()); // remove www. prefix
8183
let referrer = referrer.filter(|r| r.trim().len() > 3); // ignore empty or short referrers
@@ -109,7 +111,7 @@ fn process_event(
109111

110112
let path = url.path().to_string();
111113
let path = if path.len() > 1 && path.ends_with('/') { path.trim_end_matches('/').to_string() } else { path };
112-
let fqdn = url.host().unwrap_or_default().to_string();
114+
let fqdn = url.host_str().unwrap_or_default().to_string();
113115

114116
let event = Event {
115117
visitor_id,

web/src/components/dialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const Dialog = ({
3939
<Dia.Viewport>
4040
{showClose && (
4141
<Dia.Close className={styles.close}>
42-
<XIcon size="24" />
42+
<XIcon color="black" size="24" />
4343
</Dia.Close>
4444
)}
4545
<Dia.Popup>

web/src/components/worldmap/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ export const Worldmap = ({ metric, data }: { metric: Metric; data?: DimensionTab
7575
countries={countries}
7676
biggest={biggest}
7777
onSetLocation={setCurrentLocation}
78+
metric={metric}
7879
/>
7980
)),
80-
[countries, biggest],
81+
[countries, biggest, metric],
8182
);
8283

8384
const resetZoom = () => {
@@ -127,11 +128,13 @@ const Landmass = ({
127128
countries,
128129
biggest,
129130
onSetLocation,
131+
metric,
130132
}: {
131133
feature: (typeof features)[number];
132134
countries: Map<string, number>;
133135
biggest?: DimensionTableRow;
134136
onSetLocation: (location: Location | null) => void;
137+
metric: Metric;
135138
}) => {
136139
const percent = useMemo(
137140
() => (countries.get(feature.iso) ?? 0) / (biggest?.value ?? 100),
@@ -142,6 +145,8 @@ const Landmass = ({
142145
<path
143146
d={feature.path || ""}
144147
className={styles.geo}
148+
data-inverted={metric === "bounce_rate"}
149+
data-ignored={metric === "bounce_rate" && (percent === 0 || percent === 100)}
145150
style={{ "--percent": percent } as React.CSSProperties}
146151
onMouseEnter={() => onSetLocation({ name: feature.name, iso: feature.iso })}
147152
onMouseLeave={() => onSetLocation(null)}

web/src/components/worldmap/map.module.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ button.reset {
4545
&:hover {
4646
opacity: 0.8;
4747
}
48+
49+
&[data-inverted="true"] {
50+
/* same fill, but closer to 100 means worse instead of better */
51+
fill: hsl(94, calc(0% + 80% * (1 - var(--percent)) * (1 - var(--percent))), calc(40% + 4% * (1 - var(--percent))));
52+
}
53+
54+
&[data-ignored="true"] {
55+
fill: hsl(94, 0%, 28%);
56+
}
4857
}
4958

5059
div.tooltip {

0 commit comments

Comments
 (0)