fix toggle behavior and reorder replays table with play at beginning. Add missing messages script
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
"update-db": "prisma migrate deploy",
|
"update-db": "prisma migrate deploy",
|
||||||
"check-db": "node scripts/check-db.js",
|
"check-db": "node scripts/check-db.js",
|
||||||
"check-env": "node scripts/check-env.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",
|
"copy-db-files": "node scripts/copy-db-files.js",
|
||||||
"download-country-names": "node scripts/download-country-names.js",
|
"download-country-names": "node scripts/download-country-names.js",
|
||||||
"download-language-names": "node scripts/download-language-names.js",
|
"download-language-names": "node scripts/download-language-names.js",
|
||||||
|
|||||||
@@ -250,6 +250,7 @@
|
|||||||
"remaining": "Remaining",
|
"remaining": "Remaining",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"remove-member": "Remove member",
|
"remove-member": "Remove member",
|
||||||
|
"play": "Play",
|
||||||
"replay": "Replay",
|
"replay": "Replay",
|
||||||
"replay-id": "Replay ID",
|
"replay-id": "Replay ID",
|
||||||
"replay-enabled": "Replay enabled",
|
"replay-enabled": "Replay enabled",
|
||||||
|
|||||||
@@ -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 (
|
return (
|
||||||
<DataTable {...props}>
|
<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">
|
<DataColumn id="id" label={t(labels.session)} width="100px">
|
||||||
{(row: any) => (
|
{(row: any) => (
|
||||||
<Link href={updateParams({ session: row.sessionId })}>
|
<Link href={updateParams({ session: row.sessionId })}>
|
||||||
@@ -63,15 +72,6 @@ export function ReplaysTable({ ...props }: DataTableProps) {
|
|||||||
<DataColumn id="createdAt" label={t(labels.recordedAt)} width="140px">
|
<DataColumn id="createdAt" label={t(labels.recordedAt)} width="140px">
|
||||||
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
|
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
|
||||||
</DataColumn>
|
</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>
|
</DataTable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -19,14 +19,6 @@ export function SessionReplaysTable({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable {...props}>
|
<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">
|
<DataColumn id="play" label="" width="80px">
|
||||||
{(row: any) => (
|
{(row: any) => (
|
||||||
<Button
|
<Button
|
||||||
@@ -39,6 +31,14 @@ export function SessionReplaysTable({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</DataColumn>
|
</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>
|
</DataTable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,28 @@ export function WebsiteReplaySettings({ websiteId }: { websiteId: string }) {
|
|||||||
const [maxDuration, setMaxDuration] = useState(String(config.maxDuration ?? 300000));
|
const [maxDuration, setMaxDuration] = useState(String(config.maxDuration ?? 300000));
|
||||||
const [blockSelector, setBlockSelector] = useState(config.blockSelector ?? '');
|
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 () => {
|
const handleSave = async () => {
|
||||||
await mutateAsync(
|
await mutateAsync(
|
||||||
{
|
{
|
||||||
@@ -47,7 +69,7 @@ export function WebsiteReplaySettings({ websiteId }: { websiteId: string }) {
|
|||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
toast(t(messages.saved));
|
toast(t(messages.saved));
|
||||||
touch('websites');
|
touch('websites');
|
||||||
touch(`website:${website.id}`);
|
touch(`website:${websiteId}`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -56,7 +78,7 @@ export function WebsiteReplaySettings({ websiteId }: { websiteId: string }) {
|
|||||||
return (
|
return (
|
||||||
<Column gap="4">
|
<Column gap="4">
|
||||||
<Label>{t(labels.replays)}</Label>
|
<Label>{t(labels.replays)}</Label>
|
||||||
<Switch isSelected={enabled} onChange={setEnabled}>
|
<Switch isSelected={enabled} onChange={handleToggle} isDisabled={isPending}>
|
||||||
{t(labels.replayEnabled)}
|
{t(labels.replayEnabled)}
|
||||||
</Switch>
|
</Switch>
|
||||||
{enabled && (
|
{enabled && (
|
||||||
@@ -92,13 +114,13 @@ export function WebsiteReplaySettings({ websiteId }: { websiteId: string }) {
|
|||||||
<Label>{t(labels.blockSelector)}</Label>
|
<Label>{t(labels.blockSelector)}</Label>
|
||||||
<TextField value={blockSelector} onChange={setBlockSelector} />
|
<TextField value={blockSelector} onChange={setBlockSelector} />
|
||||||
</Column>
|
</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>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -358,6 +358,7 @@ export const labels: Record<string, string> = {
|
|||||||
needsImprovement: 'label.needs-improvement',
|
needsImprovement: 'label.needs-improvement',
|
||||||
poor: 'label.poor',
|
poor: 'label.poor',
|
||||||
sampleSize: 'label.sample-size',
|
sampleSize: 'label.sample-size',
|
||||||
|
play: 'label.play',
|
||||||
replays: 'label.replays',
|
replays: 'label.replays',
|
||||||
replay: 'label.replay',
|
replay: 'label.replay',
|
||||||
replayId: 'label.replay-id',
|
replayId: 'label.replay-id',
|
||||||
|
|||||||
Reference in New Issue
Block a user