mirror of
https://github.com/samber/awesome-prometheus-alerts.git
synced 2026-06-25 02:46:59 +08:00
feat: track sponsor clicks with blocking event before navigation
- Add recordCopy() for copy events (bumps session/lifetime counters) - Add recordAndWait() for blocking events (1500ms timeout, errors swallowed) - Extract shared sponsor click handler into site/src/scripts/sponsor.ts - Plain left-click blocks navigation until HTTP response; modifier/middle clicks track fire-and-forget and let the browser navigate natively - Distinguish header vs footer placement via data-sponsor-slot attribute
This commit is contained in:
parent
1c5f626046
commit
5a5976c9a3
5 changed files with 86 additions and 16 deletions
|
|
@ -76,7 +76,7 @@ const featuredGroups = data.groups.filter((g) => featuredGroupSlugs.includes(get
|
||||||
<h3 class="text-sm font-semibold text-slate-900 dark:text-white mb-3">Sponsors</h3>
|
<h3 class="text-sm font-semibold text-slate-900 dark:text-white mb-3">Sponsors</h3>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
{sponsors.map((s) => (
|
{sponsors.map((s) => (
|
||||||
<a href={s.url} target="_blank" rel="noopener noreferrer" class="block hover:opacity-80 transition-opacity">
|
<a href={s.url} target="_blank" rel="noopener noreferrer" class="block hover:opacity-80 transition-opacity" data-sponsor-name={s.name} data-sponsor-slot="footer">
|
||||||
<img src={`${base}${s.logo}`} alt={`${s.name} — ${s.description}`} class="h-6" />
|
<img src={`${base}${s.logo}`} alt={`${s.name} — ${s.description}`} class="h-6" />
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
|
|
@ -98,3 +98,8 @@ const featuredGroups = data.groups.filter((g) => featuredGroupSlugs.includes(get
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { initSponsorClickTracking } from '../scripts/sponsor';
|
||||||
|
initSponsorClickTracking();
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ const starsLabel = stars > 0 ? (stars >= 1000 ? `${(stars / 1000).toFixed(1)}k`
|
||||||
<div class="max-w-7xl mx-auto flex items-center justify-center gap-3">
|
<div class="max-w-7xl mx-auto flex items-center justify-center gap-3">
|
||||||
<span class="text-xs font-medium tracking-wider uppercase text-slate-400 dark:text-slate-500">Sponsored by</span>
|
<span class="text-xs font-medium tracking-wider uppercase text-slate-400 dark:text-slate-500">Sponsored by</span>
|
||||||
{sponsors.map((s) => (
|
{sponsors.map((s) => (
|
||||||
<a href={s.url} target="_blank" rel="noopener noreferrer" class="hover:opacity-75 transition-opacity" title={s.name}>
|
<a href={s.url} target="_blank" rel="noopener noreferrer" class="hover:opacity-75 transition-opacity" title={s.name} data-sponsor-name={s.name} data-sponsor-slot="header">
|
||||||
<img src={`${base}${s.logo}`} alt={`${s.name} — ${s.description}`} class="h-10 w-auto" />
|
<img src={`${base}${s.logo}`} alt={`${s.name} — ${s.description}`} class="h-10 w-auto" />
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
|
|
@ -149,6 +149,11 @@ const starsLabel = stars > 0 ? (stars >= 1000 ? `${(stars / 1000).toFixed(1)}k`
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { initSponsorClickTracking } from '../scripts/sponsor';
|
||||||
|
initSponsorClickTracking();
|
||||||
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const btn = document.getElementById('mobile-menu-btn');
|
const btn = document.getElementById('mobile-menu-btn');
|
||||||
const menu = document.getElementById('mobile-menu');
|
const menu = document.getElementById('mobile-menu');
|
||||||
|
|
|
||||||
|
|
@ -99,10 +99,10 @@ const canonical = canonicalUrl ?? `${SITE_ORIGIN}${base}${Astro.url.pathname.rep
|
||||||
<Footer base={base} />
|
<Footer base={base} />
|
||||||
<StarToast />
|
<StarToast />
|
||||||
<script>
|
<script>
|
||||||
import { record } from '../scripts/pipe';
|
import { recordCopy } from '../scripts/pipe';
|
||||||
window.addEventListener('apa-copy', (e: Event) => {
|
window.addEventListener('apa-copy', (e: Event) => {
|
||||||
const { name, ...data } = (e as CustomEvent<Record<string, unknown>>).detail;
|
const { name, ...data } = (e as CustomEvent<Record<string, unknown>>).detail;
|
||||||
record(name as string, data);
|
recordCopy(name as string, data);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -68,15 +68,13 @@ function getPageContext() {
|
||||||
getUserId();
|
getUserId();
|
||||||
getSessionId();
|
getSessionId();
|
||||||
|
|
||||||
export function record(name: string, data: Record<string, unknown>): void {
|
function buildPayload(name: string, data: Record<string, unknown>): object {
|
||||||
const payload = {
|
return {
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
transaction_id: uid(),
|
transaction_id: uid(),
|
||||||
name: "awesome_prometheus_alerts_" + name,
|
name: "awesome_prometheus_alerts_" + name,
|
||||||
user_id: getUserId(),
|
user_id: getUserId(),
|
||||||
session_id: getSessionId(),
|
session_id: getSessionId(),
|
||||||
session_copy_count: bumpSessionCount(),
|
|
||||||
lifetime_copy_count: bumpLifetimeCount(),
|
|
||||||
...data,
|
...data,
|
||||||
...getPageContext(),
|
...getPageContext(),
|
||||||
language: navigator.language,
|
language: navigator.language,
|
||||||
|
|
@ -87,7 +85,10 @@ export function record(name: string, data: Record<string, unknown>): void {
|
||||||
user_agent: navigator.userAgent,
|
user_agent: navigator.userAgent,
|
||||||
is_bot: /bot|crawl|spider/i.test(navigator.userAgent),
|
is_bot: /bot|crawl|spider/i.test(navigator.userAgent),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function record(name: string, data: Record<string, unknown>): void {
|
||||||
|
const payload = buildPayload(name, data);
|
||||||
fetch(PIPE_URL + "awesome_prometheus_alerts_" + name, {
|
fetch(PIPE_URL + "awesome_prometheus_alerts_" + name, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
|
|
@ -95,3 +96,29 @@ export function record(name: string, data: Record<string, unknown>): void {
|
||||||
keepalive: true,
|
keepalive: true,
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function recordCopy(name: string, data: Record<string, unknown>): void {
|
||||||
|
record(name, {
|
||||||
|
session_copy_count: bumpSessionCount(),
|
||||||
|
lifetime_copy_count: bumpLifetimeCount(),
|
||||||
|
...data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function recordAndWait(name: string, data: Record<string, unknown>): Promise<void> {
|
||||||
|
const payload = buildPayload(name, data);
|
||||||
|
const ctrl = new AbortController();
|
||||||
|
const timer = setTimeout(() => ctrl.abort(), 1500);
|
||||||
|
try {
|
||||||
|
await fetch(PIPE_URL + "awesome_prometheus_alerts_" + name, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
headers: { Authorization: `Bearer ${PIPE_KEY}` },
|
||||||
|
signal: ctrl.signal,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// swallow — a failed event must never break sponsor navigation
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
33
site/src/scripts/sponsor.ts
Normal file
33
site/src/scripts/sponsor.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { record, recordAndWait } from './pipe';
|
||||||
|
|
||||||
|
export function initSponsorClickTracking(): void {
|
||||||
|
document.querySelectorAll<HTMLAnchorElement>('a[data-sponsor-name]').forEach((a) => {
|
||||||
|
a.addEventListener('click', async (e) => {
|
||||||
|
const me = e as MouseEvent;
|
||||||
|
const href = a.href;
|
||||||
|
const sponsorName = a.dataset.sponsorName!;
|
||||||
|
const sponsorSlot = a.dataset.sponsorSlot!;
|
||||||
|
const eventData = { sponsor_name: sponsorName, sponsor_url: href, sponsor_slot: sponsorSlot };
|
||||||
|
|
||||||
|
// Modifier / non-primary clicks: track fire-and-forget, let browser handle navigation
|
||||||
|
if (me.button !== 0 || me.metaKey || me.ctrlKey || me.shiftKey) {
|
||||||
|
record('sponsor_click', eventData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plain left-click: block navigation until event is recorded
|
||||||
|
e.preventDefault();
|
||||||
|
// Open blank tab now (inside user gesture) to avoid popup-blocker after await
|
||||||
|
const w = a.target === '_blank' ? window.open('', '_blank') : null;
|
||||||
|
try {
|
||||||
|
await recordAndWait('sponsor_click', eventData);
|
||||||
|
} finally {
|
||||||
|
if (w) {
|
||||||
|
w.location.href = href;
|
||||||
|
} else {
|
||||||
|
window.open(href, '_blank') ?? (window.location.href = href);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue