This commit is contained in:
2023-06-21 20:18:19 +02:00
parent c1c58118a2
commit c1faa879a3
7 changed files with 99 additions and 47 deletions
-1
View File
@@ -2,7 +2,6 @@ import { metricsDb } from '$lib/server/metrics';
import { tests as testsStore, type Test, userTests } from '$lib/server/tests'
import { get } from 'svelte/store'
export async function load({ cookies, params }) {
// Set a random session ID cookie if there is none, so we can uniquely
// identify the user and not randomly change their experience on every page load.
-25
View File
@@ -80,29 +80,4 @@ export const actions = {
return { success: true }
}
}
type StructuredFormData =
| string
| boolean
| number
| File
| StructuredFormData[];
function formBody(body: FormData) {
return [...body.entries()].reduce((data, [k, v]) => {
let value: StructuredFormData = v;
if (v === "true") value = true;
if (v === "false") value = false;
if (!isNaN(Number(v))) value = Number(v);
// For grouped fields like multi-selects and checkboxes, we need to
// store the values in an array.
if (k in data) {
const val = data[k];
value = Array.isArray(val) ? [...val, value] : [val, value];
}
data[k] = value;
return data;
}, {} as Record<string, StructuredFormData>);
}
+22 -2
View File
@@ -2,7 +2,6 @@
import { enhance } from '$app/forms'
import { page } from '$app/stores'
import type { Test } from '$lib/server/tests'
import { tick } from 'svelte'
import AB from './AB.svelte'
export let data
@@ -37,6 +36,27 @@
otherTests.forEach((t) => (t.chance = Math.round(Math.max(0, Math.min(t.chance + diffPerTest, 1) * 100)) / 100))
}
// Calculate max views and duration across all tests so we can scale the charts accordingly
let globalAnalytics: {
maxViews: number
maxDuration: number
}
$: globalAnalytics = data.tests.reduce(
(acc, test) => {
if (test.analytics) {
if (test.analytics?.views > acc.maxViews) {
acc.maxViews = test.analytics.views
}
if (test.analytics?.duration > acc.maxDuration) {
acc.maxDuration = test.analytics.duration
}
}
return acc
},
{ maxViews: 0, maxDuration: 0 }
)
// Prevent accidental form submission on enter
function preventSubmit(e: KeyboardEvent) {
if (e.key === 'Enter') {
e.preventDefault()
@@ -52,7 +72,7 @@
<div class="mt-6">
<div class="flex flex-col gap-6">
{#each data.tests as test, index (test.id)}
<AB i={index} bind:data={test} on:delete={() => deleteTest(index)} on:chanceChanged={() => onChanceChanged(test)} />
<AB i={index} bind:data={test} bind:globalAnalytics on:delete={() => deleteTest(index)} on:chanceChanged={() => onChanceChanged(test)} />
{/each}
</div>
</div>
+61 -17
View File
@@ -3,12 +3,13 @@
import Editor from '@tinymce/tinymce-svelte'
import { createEventDispatcher } from 'svelte'
import type { Test } from '$lib/server/tests'
import { tick } from 'svelte'
import { onMount } from 'svelte'
const dispatch = createEventDispatcher()
export let i: number
export let data: Test & { analytics?: { views: number; duration: number } }
export let globalAnalytics: { maxViews: number; maxDuration: number }
function deleteSelf(this: HTMLButtonElement) {
if (this.innerHTML !== 'Delete?') {
@@ -33,19 +34,56 @@
}
const enabled = data.enabled
let chartViews: HTMLDivElement
let chartDuration: HTMLDivElement
onMount(async () => {
const Plotly = await import('plotly.js-dist')
Plotly.newPlot(chartViews, [
{
domain: { x: [0, 1], y: [0, 1] },
value: data.analytics?.views,
title: { text: 'Page Views' },
type: 'indicator',
mode: 'gauge+number',
gauge: {
axis: { range: [0, globalAnalytics.maxViews] },
bgcolor: 'white',
}
},
], {
width: 400, height: 230, margin: { t: 0, b: 0 }, paper_bgcolor: 'transparent'
})
Plotly.newPlot(chartDuration, [
{
domain: { x: [0, 1], y: [0, 1] },
value: data.analytics?.duration,
title: { text: 'Average Duration (s)' },
type: 'indicator',
mode: 'gauge+number',
gauge: {
axis: { range: [0, globalAnalytics.maxDuration] },
bgcolor: 'white',
}
},
], {
width: 400, height: 230, margin: { t: 0, b: 0 }, paper_bgcolor: 'transparent'
})
})
</script>
<details class="collapse collapse-arrow rounded-lg" open={enabled} >
<summary class="collapse-title text-xl font-medium leading-tight bg-base-300 min-h-min">
<div class="flex items-center gap-2">
<input type="checkbox" name="test[{i}][enabled]" class="toggle toggle-accent" bind:checked={data.enabled} />
<input type="hidden" name="test[{i}][enabled]" value='off' />
<span style="color: {data.color}">{data.title}</span>
<!-- <span>{data.id}</span> -->
<input type="range" name="test[{i}][chance]" class="range max-w-xs ml-20" min="0" max="1" step="0.01" bind:value={data.chance} on:input={() => chancePercent = Math.round(data.chance * 100)} on:input={chanceChanged} />
<input type="text" class="input input-ghost w-16" bind:value={chancePercent} on:input={() => data.chance = chancePercent / 100} on:input={chanceChanged} />
<!-- <span>{Math.round(data.chance * 100)}%</span> -->
<!-- <span>{data.chance}</span> -->
<details class="collapse-arrow collapse rounded-lg" open={enabled}>
<summary class="collapse-title min-h-min bg-base-300 text-xl font-medium leading-tight">
<div class="flex gap-2">
<div class="flex items-center basis-1/4 flex-none overflow-hidden">
<input type="checkbox" name="test[{i}][enabled]" class="toggle-accent toggle" bind:checked={data.enabled} />
<input type="hidden" name="test[{i}][enabled]" value="off" />
<span class="ml-2 overflow-hidden text-ellipsis" style="color: {data.color}">{data.title}</span>
</div>
<div class="flex items-center">
<input type="range" name="test[{i}][chance]" class="range max-w-xs" min="0" max="1" step="0.01" bind:value={data.chance} on:input={() => (chancePercent = Math.round(data.chance * 100))} on:input={chanceChanged} />
<input type="text" class="input-ghost input w-16" bind:value={chancePercent} on:input={() => (data.chance = chancePercent / 100)} on:input={chanceChanged} />
</div>
</div>
</summary>
<div class="collapse-content flex w-full flex-col flex-wrap gap-4 bg-base-200">
@@ -57,12 +95,13 @@
</label>
<input type="hidden" name="test[{i}][color]" value={data.color} />
<ColorPicker bind:hex={data.color} isOpen={true} isPopup={false} isInput={false} />
</div>
</div>
<div class="grow">
<div class="form-control w-full">
<label class="label">
<span class="label-text">Title
<span class="label-text"
>Title
{#if data.title.length == 0}
<span class="text-red-500"> (required)</span>
{/if}
@@ -73,7 +112,8 @@
<div class="form-control w-full">
<label class="label">
<span class="label-text">Body
<span class="label-text"
>Body
{#if data.body.length == 0}
<span class="text-red-500"> (required)</span>
{/if}
@@ -97,8 +137,12 @@
{#if data.analytics}
<div>
<h1 class="text-2xl font-bold">Analytics</h1>
<p>Page views: {data.analytics.views}</p>
<p>Average duration: {isNaN(data.analytics.duration) ? '???' : Math.round(data.analytics.duration)} sec</p>
<div class="flex flex-row justify-center">
<div bind:this={chartViews} />
<div bind:this={chartDuration} />
</div>
<!-- <p>Page views: {data.analytics.views}</p>
<p>Average duration: {isNaN(data.analytics.duration) ? '???' : Math.round(data.analytics.duration)} sec</p> -->
</div>
{/if}
</div>
@@ -1,5 +1,4 @@
import { metricsDb } from "$lib/server/metrics";
import { json } from "@sveltejs/kit";
import { metricsDb } from "$lib/server/metrics"
export async function POST({ params, cookies, request }) {
// Update metrics page view end time