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",
|
||||
"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",
|
||||
|
||||
@@ -250,6 +250,7 @@
|
||||
"remaining": "Remaining",
|
||||
"remove": "Remove",
|
||||
"remove-member": "Remove member",
|
||||
"play": "Play",
|
||||
"replay": "Replay",
|
||||
"replay-id": "Replay ID",
|
||||
"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 (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user