fix toggle behavior and reorder replays table with play at beginning. Add missing messages script

This commit is contained in:
Francis Cao
2026-03-05 13:31:00 -08:00
parent 72bb63fe5e
commit 38577a9c0c
7 changed files with 109 additions and 24 deletions
+1
View File
@@ -33,6 +33,7 @@
"update-db": "prisma migrate deploy",
"check-db": "node scripts/check-db.js",
"check-env": "node scripts/check-env.js",
"check-missing-messages": "node scripts/check-missing-messages.js",
"copy-db-files": "node scripts/copy-db-files.js",
"download-country-names": "node scripts/download-country-names.js",
"download-language-names": "node scripts/download-language-names.js",
+1
View File
@@ -250,6 +250,7 @@
"remaining": "Remaining",
"remove": "Remove",
"remove-member": "Remove member",
"play": "Play",
"replay": "Replay",
"replay-id": "Replay ID",
"replay-enabled": "Replay enabled",
+60
View File
@@ -0,0 +1,60 @@
import { readdirSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const messagesDir = join(__dirname, '..', 'public', 'intl', 'messages');
const en = JSON.parse(readFileSync(join(messagesDir, 'en-US.json'), 'utf8'));
// Flatten nested structure: { label: { foo: 'bar' } } -> { 'label.foo': 'bar' }
function flatten(obj, prefix = '') {
const result = {};
for (const [k, v] of Object.entries(obj)) {
const key = prefix ? `${prefix}.${k}` : k;
if (typeof v === 'object' && v !== null) {
Object.assign(result, flatten(v, key));
} else {
result[key] = v;
}
}
return result;
}
const enFlat = flatten(en);
const enKeys = Object.keys(enFlat);
console.log(`en-US.json has ${enKeys.length} keys`);
const files = readdirSync(messagesDir)
.filter(f => f.endsWith('.json') && f !== 'en-US.json')
.sort();
const allMissing = {};
let total = 0;
for (const fname of files) {
const data = JSON.parse(readFileSync(join(messagesDir, fname), 'utf8'));
const flat = flatten(data);
const missing = enKeys.filter(k => !(k in flat));
if (missing.length) {
allMissing[fname] = missing;
console.log(`${fname}: ${missing.length} missing`);
total += missing.length;
}
}
console.log(`\nTotal missing across all locales: ${total}`);
const keyCounts = {};
for (const missing of Object.values(allMissing)) {
for (const k of missing) {
keyCounts[k] = (keyCounts[k] || 0) + 1;
}
}
const sorted = Object.entries(keyCounts).sort((a, b) => b[1] - a[1]);
if (sorted.length) {
console.log('\nMost commonly missing keys:');
for (const [k, count] of sorted) {
console.log(` "${k}": missing from ${count} files (en value: "${enFlat[k]}")`);
}
}
@@ -20,6 +20,15 @@ export function ReplaysTable({ ...props }: DataTableProps) {
return (
<DataTable {...props}>
<DataColumn id="play" label="" width="80px">
{(row: any) => (
<Button variant="quiet" onClick={() => router.push(updateParams({ replay: row.id }))}>
<Icon>
<Play />
</Icon>
</Button>
)}
</DataColumn>
<DataColumn id="id" label={t(labels.session)} width="100px">
{(row: any) => (
<Link href={updateParams({ session: row.sessionId })}>
@@ -63,15 +72,6 @@ export function ReplaysTable({ ...props }: DataTableProps) {
<DataColumn id="createdAt" label={t(labels.recordedAt)} width="140px">
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
</DataColumn>
<DataColumn id="play" label="" width="80px">
{(row: any) => (
<Button variant="quiet" onClick={() => router.push(updateParams({ replay: row.id }))}>
<Icon>
<Play />
</Icon>
</Button>
)}
</DataColumn>
</DataTable>
);
}
@@ -19,14 +19,6 @@ export function SessionReplaysTable({
return (
<DataTable {...props}>
<DataColumn id="id" label={t(labels.replayId)} />
<DataColumn id="duration" label={t(labels.duration)} width="100px">
{(row: any) => formatDuration(row.duration || 0)}
</DataColumn>
<DataColumn id="eventCount" label={t(labels.actions)} width="80px" />
<DataColumn id="createdAt" label={t(labels.recordedAt)} width="140px">
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
</DataColumn>
<DataColumn id="play" label="" width="80px">
{(row: any) => (
<Button
@@ -39,6 +31,14 @@ export function SessionReplaysTable({
</Button>
)}
</DataColumn>
<DataColumn id="id" label={t(labels.replayId)} />
<DataColumn id="duration" label={t(labels.duration)} width="100px">
{(row: any) => formatDuration(row.duration || 0)}
</DataColumn>
<DataColumn id="eventCount" label={t(labels.actions)} width="80px" />
<DataColumn id="createdAt" label={t(labels.recordedAt)} width="140px">
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
</DataColumn>
</DataTable>
);
}
@@ -32,6 +32,28 @@ export function WebsiteReplaySettings({ websiteId }: { websiteId: string }) {
const [maxDuration, setMaxDuration] = useState(String(config.maxDuration ?? 300000));
const [blockSelector, setBlockSelector] = useState(config.blockSelector ?? '');
const handleToggle = async (value: boolean) => {
const previous = enabled;
setEnabled(value);
try {
await mutateAsync(
{
replayEnabled: value,
},
{
onSuccess: async () => {
toast(t(messages.saved));
touch('websites');
touch(`website:${websiteId}`);
},
},
);
} catch {
setEnabled(previous);
}
};
const handleSave = async () => {
await mutateAsync(
{
@@ -47,7 +69,7 @@ export function WebsiteReplaySettings({ websiteId }: { websiteId: string }) {
onSuccess: async () => {
toast(t(messages.saved));
touch('websites');
touch(`website:${website.id}`);
touch(`website:${websiteId}`);
},
},
);
@@ -56,7 +78,7 @@ export function WebsiteReplaySettings({ websiteId }: { websiteId: string }) {
return (
<Column gap="4">
<Label>{t(labels.replays)}</Label>
<Switch isSelected={enabled} onChange={setEnabled}>
<Switch isSelected={enabled} onChange={handleToggle} isDisabled={isPending}>
{t(labels.replayEnabled)}
</Switch>
{enabled && (
@@ -92,13 +114,13 @@ export function WebsiteReplaySettings({ websiteId }: { websiteId: string }) {
<Label>{t(labels.blockSelector)}</Label>
<TextField value={blockSelector} onChange={setBlockSelector} />
</Column>
<Row>
<Button variant="primary" onPress={handleSave} isDisabled={isPending}>
{t(labels.save)}
</Button>
</Row>
</>
)}
<Row>
<Button variant="primary" onPress={handleSave} isDisabled={isPending}>
{t(labels.save)}
</Button>
</Row>
</Column>
);
}
+1
View File
@@ -358,6 +358,7 @@ export const labels: Record<string, string> = {
needsImprovement: 'label.needs-improvement',
poor: 'label.poor',
sampleSize: 'label.sample-size',
play: 'label.play',
replays: 'label.replays',
replay: 'label.replay',
replayId: 'label.replay-id',