Skip to content

Commit df0258f

Browse files
committed
comments
1 parent 0f9e5f6 commit df0258f

File tree

12 files changed

+186
-76
lines changed

12 files changed

+186
-76
lines changed

apps/api/src/controllers/gsc-oauth-callback.controller.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
44
import { z } from 'zod';
55
import { LogError } from '@/utils/errors';
66

7+
const OAUTH_SENSITIVE_KEYS = ['code', 'state'];
8+
9+
function sanitizeOAuthQuery(
10+
query: Record<string, unknown> | null | undefined
11+
): Record<string, string> {
12+
if (!query || typeof query !== 'object') {
13+
return {};
14+
}
15+
return Object.fromEntries(
16+
Object.entries(query).map(([k, v]) => [
17+
k,
18+
OAUTH_SENSITIVE_KEYS.includes(k) ? '<redacted>' : String(v),
19+
])
20+
);
21+
}
22+
723
export async function gscGoogleCallback(
824
req: FastifyRequest,
925
reply: FastifyReply
@@ -16,27 +32,35 @@ export async function gscGoogleCallback(
1632

1733
const query = schema.safeParse(req.query);
1834
if (!query.success) {
19-
throw new LogError('Invalid GSC callback query params', {
20-
error: query.error,
21-
query: req.query,
22-
});
35+
throw new LogError(
36+
'Invalid GSC callback query params',
37+
sanitizeOAuthQuery(req.query as Record<string, unknown>)
38+
);
2339
}
2440

2541
const { code, state } = query.data;
2642
const storedState = req.cookies.gsc_oauth_state ?? null;
2743
const codeVerifier = req.cookies.gsc_code_verifier ?? null;
2844
const projectId = req.cookies.gsc_project_id ?? null;
2945

30-
if (!storedState || !codeVerifier || !projectId) {
46+
const hasStoredState = storedState !== null;
47+
const hasCodeVerifier = codeVerifier !== null;
48+
const hasProjectId = projectId !== null;
49+
const hasAllCookies = hasStoredState && hasCodeVerifier && hasProjectId;
50+
if (!hasAllCookies) {
3151
throw new LogError('Missing GSC OAuth cookies', {
32-
storedState: storedState === null,
33-
codeVerifier: codeVerifier === null,
34-
projectId: projectId === null,
52+
storedState: !hasStoredState,
53+
codeVerifier: !hasCodeVerifier,
54+
projectId: !hasProjectId,
3555
});
3656
}
3757

3858
if (state !== storedState) {
39-
throw new LogError('GSC OAuth state mismatch', { state, storedState });
59+
throw new LogError('GSC OAuth state mismatch', {
60+
hasState: true,
61+
hasStoredState: true,
62+
stateMismatch: true,
63+
});
4064
}
4165

4266
const tokens = await googleGsc.validateAuthorizationCode(
@@ -91,6 +115,9 @@ export async function gscGoogleCallback(
91115
return reply.redirect(redirectUrl);
92116
} catch (error) {
93117
req.log.error(error);
118+
reply.clearCookie('gsc_oauth_state');
119+
reply.clearCookie('gsc_code_verifier');
120+
reply.clearCookie('gsc_project_id');
94121
return redirectWithError(reply, error);
95122
}
96123
}

apps/start/src/components/page/gsc-breakdown-table.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function GscBreakdownTable({ projectId, value, type }: GscBreakdownTableP
4343

4444
const breakdownKey = type === 'page' ? 'query' : 'page';
4545
const breakdownLabel = type === 'page' ? 'Query' : 'Page';
46+
const pluralLabel = type === 'page' ? 'queries' : 'pages';
4647

4748
const maxClicks = Math.max(
4849
...(breakdownRows as { clicks: number }[]).map((r) => r.clicks),
@@ -52,7 +53,7 @@ export function GscBreakdownTable({ projectId, value, type }: GscBreakdownTableP
5253
return (
5354
<div className="card overflow-hidden">
5455
<div className="border-b p-4">
55-
<h3 className="font-medium text-sm">Top {breakdownLabel.toLowerCase()}s</h3>
56+
<h3 className="font-medium text-sm">Top {pluralLabel}</h3>
5657
</div>
5758
{isLoading ? (
5859
<OverviewWidgetTable

apps/start/src/components/page/gsc-cannibalization.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import type { IChartRange, IInterval } from '@openpanel/validation';
12
import { keepPreviousData, useQuery } from '@tanstack/react-query';
23
import { AlertCircleIcon, ChevronsUpDownIcon } from 'lucide-react';
3-
import { useMemo, useState } from 'react';
4+
import { useEffect, useMemo, useState } from 'react';
45
import { Pagination } from '@/components/pagination';
56
import { useAppContext } from '@/hooks/use-app-context';
67
import { useTRPC } from '@/integrations/trpc/react';
@@ -9,8 +10,8 @@ import { cn } from '@/utils/cn';
910

1011
interface GscCannibalizationProps {
1112
projectId: string;
12-
range: string;
13-
interval: string;
13+
range: IChartRange;
14+
interval: IInterval;
1415
startDate?: string;
1516
endDate?: string;
1617
}
@@ -30,7 +31,13 @@ export function GscCannibalization({
3031

3132
const query = useQuery(
3233
trpc.gsc.getCannibalization.queryOptions(
33-
{ projectId, range: range as any, interval: interval as any },
34+
{
35+
projectId,
36+
range,
37+
interval,
38+
startDate,
39+
endDate,
40+
},
3441
{ placeholderData: keepPreviousData }
3542
)
3643
);
@@ -50,6 +57,9 @@ export function GscCannibalization({
5057
const items = query.data ?? [];
5158

5259
const pageCount = Math.ceil(items.length / pageSize) || 1;
60+
useEffect(() => {
61+
setPage((p) => Math.max(0, Math.min(p, pageCount - 1)));
62+
}, [items, pageSize, pageCount]);
5363
const paginatedItems = useMemo(
5464
() => items.slice(page * pageSize, (page + 1) * pageSize),
5565
[items, page, pageSize]
@@ -99,7 +109,6 @@ export function GscCannibalization({
99109
))}
100110
{paginatedItems.map((item) => {
101111
const isOpen = expanded.has(item.query);
102-
const winner = item.pages[0];
103112
const avgCtr =
104113
item.pages.reduce((s, p) => s + p.ctr, 0) / item.pages.length;
105114

apps/start/src/components/pages/page-sparkline.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface SparklineBarsProps {
88
data: { date: string; pageviews: number }[];
99
}
1010

11-
const gap = 1;
11+
const defaultGap = 1;
1212
const height = 24;
1313
const width = 100;
1414

@@ -38,7 +38,16 @@ function SparklineBars({ data }: SparklineBarsProps) {
3838
}
3939
const max = Math.max(...data.map((d) => d.pageviews), 1);
4040
const total = data.length;
41-
const barW = Math.max(2, Math.floor((width - gap * (total - 1)) / total));
41+
// Compute bar width to fit SVG width; reduce gap if needed so barW >= 1 when possible
42+
let gap = defaultGap;
43+
let barW = Math.floor((width - gap * (total - 1)) / total);
44+
if (barW < 1 && total > 1) {
45+
gap = 0;
46+
barW = Math.floor((width - gap * (total - 1)) / total);
47+
}
48+
if (barW < 1) {
49+
barW = 1;
50+
}
4251
const trend = getTrendDirection(data);
4352
const trendColor =
4453
trend === '↑'
@@ -71,9 +80,9 @@ function SparklineBars({ data }: SparklineBarsProps) {
7180
<Tooltiper
7281
content={
7382
trend === '↑'
74-
? 'Upgoing trend'
83+
? 'Upward trend'
7584
: trend === '↓'
76-
? 'Downgoing trend'
85+
? 'Downward trend'
7786
: 'Stable trend'
7887
}
7988
>

apps/start/src/components/pages/table/columns.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ export function useColumns({
103103
if (prev == null) {
104104
return <span className="text-muted-foreground"></span>;
105105
}
106+
if (prev === 0) {
107+
return (
108+
<div className="flex items-center gap-2">
109+
<span className="font-mono text-sm tabular-nums">
110+
{number.short(row.original.sessions)}
111+
</span>
112+
<span className="text-muted-foreground">new</span>
113+
</div>
114+
);
115+
}
106116

107117
const pct = ((row.original.sessions - prev) / prev) * 100;
108118
const isPos = pct >= 0;
@@ -112,15 +122,12 @@ export function useColumns({
112122
<span className="font-mono text-sm tabular-nums">
113123
{number.short(row.original.sessions)}
114124
</span>
115-
{prev === 0 && <span className="text-muted-foreground">new</span>}
116-
{prev > 0 && (
117-
<span
118-
className={`font-mono text-sm tabular-nums ${isPos ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'}`}
119-
>
120-
{isPos ? '+' : ''}
121-
{pct.toFixed(1)}%
122-
</span>
123-
)}
125+
<span
126+
className={`font-mono text-sm tabular-nums ${isPos ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'}`}
127+
>
128+
{isPos ? '+' : ''}
129+
{pct.toFixed(1)}%
130+
</span>
124131
</div>
125132
);
126133
},

apps/start/src/components/pages/table/index.tsx

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ export function PagesTable({ projectId }: PagesTableProps) {
2626

2727
const pagesQuery = useQuery(
2828
trpc.event.pages.queryOptions(
29-
{ projectId, cursor: 1, take: 1000, search: undefined, range, interval },
29+
{
30+
projectId,
31+
search: debouncedSearch ?? undefined,
32+
range,
33+
interval,
34+
},
3035
{ placeholderData: keepPreviousData },
3136
),
3237
);
@@ -44,7 +49,7 @@ export function PagesTable({ projectId }: PagesTableProps) {
4449
range,
4550
startDate: startDate ?? undefined,
4651
endDate: endDate ?? undefined,
47-
limit: 1000,
52+
limit: 10_000,
4853
},
4954
{ enabled: isGscConnected },
5055
),
@@ -88,22 +93,11 @@ export function PagesTable({ projectId }: PagesTableProps) {
8893
}));
8994
}, [pagesQuery.data, gscMap]);
9095

91-
const filteredData = useMemo(() => {
92-
if (!debouncedSearch) return rawData;
93-
const q = debouncedSearch.toLowerCase();
94-
return rawData.filter(
95-
(p) =>
96-
p.path.toLowerCase().includes(q) ||
97-
p.origin.toLowerCase().includes(q) ||
98-
(p.title ?? '').toLowerCase().includes(q),
99-
);
100-
}, [rawData, debouncedSearch]);
101-
10296
const columns = useColumns({ projectId, isGscConnected, previousMap });
10397

10498
const { table } = useTable({
10599
columns,
106-
data: filteredData,
100+
data: rawData,
107101
loading: pagesQuery.isLoading,
108102
pageSize: 50,
109103
name: 'pages',
@@ -133,7 +127,9 @@ export function PagesTable({ projectId }: PagesTableProps) {
133127
: 'Integrate our web SDK to your site to get pages here.',
134128
}}
135129
onRowClick={(row) => {
136-
if (!isGscConnected) return;
130+
if (!isGscConnected) {
131+
return;
132+
}
137133
const page = row.original;
138134
pushModal('PageDetails', {
139135
type: 'page',

0 commit comments

Comments
 (0)