Skip to content

Commit 03dde20

Browse files
committed
* Stopped key shortcuts from firing when holding down the key
* Updated Logs Page with the new implementation in time filter component * In TRQL editor users can now click on empty/blank spaces in the editor and the cursor will appear * Added CMD + / for line commenting in TRQL * Activated propper undo/redo functionality in CodeMirror (TRQL editor) * Added a check fo new logs button, previously once the user got to the end of the logs he could not check for newer logs * Added showing MS in logs page Dates * Removed LOG_INFO internal logs, they are available with Admin Debug flag * Added support for correct timezone render on server side. * Increased CLICKHOUSE_LOGS_LIST_MAX_MEMORY_USAGE to 1GB
1 parent 7781e2a commit 03dde20

File tree

15 files changed

+243
-78
lines changed

15 files changed

+243
-78
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useFetcher } from "@remix-run/react";
2+
import { useEffect, useRef } from "react";
3+
import { useTypedLoaderData } from "remix-typedjson";
4+
import type { loader } from "~/root";
5+
6+
export function TimezoneSetter() {
7+
const { timezone: storedTimezone } = useTypedLoaderData<typeof loader>();
8+
const fetcher = useFetcher();
9+
const hasSetTimezone = useRef(false);
10+
11+
useEffect(() => {
12+
if (hasSetTimezone.current) return;
13+
14+
const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
15+
16+
if (browserTimezone && browserTimezone !== storedTimezone) {
17+
hasSetTimezone.current = true;
18+
fetcher.submit(
19+
{ timezone: browserTimezone },
20+
{
21+
method: "POST",
22+
action: "/resources/timezone",
23+
encType: "application/json",
24+
}
25+
);
26+
}
27+
}, [storedTimezone, fetcher]);
28+
29+
return null;
30+
}

apps/webapp/app/components/code/CodeBlock.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ type HighlightCodeProps = {
458458
preClassName?: string;
459459
isWrapped: boolean;
460460
searchTerm?: string;
461+
containerRef?: React.RefObject<HTMLDivElement>;
461462
};
462463

463464
function HighlightCode({
@@ -471,6 +472,7 @@ function HighlightCode({
471472
preClassName,
472473
isWrapped,
473474
searchTerm,
475+
containerRef,
474476
}: HighlightCodeProps) {
475477
const [isLoaded, setIsLoaded] = useState(false);
476478

@@ -500,7 +502,7 @@ function HighlightCode({
500502

501503
if (!isLoaded) {
502504
return (
503-
<div dir="ltr" className={containerClasses}>
505+
<div dir="ltr" className={containerClasses} ref={containerRef}>
504506
<pre className={preClasses}>{code}</pre>
505507
</div>
506508
);
@@ -515,7 +517,7 @@ function HighlightCode({
515517
getLineProps,
516518
getTokenProps,
517519
}) => (
518-
<div dir="ltr" className={containerClasses}>
520+
<div dir="ltr" className={containerClasses} ref={containerRef}>
519521
<pre className={cn(preClasses, inheritedClassName)} style={inheritedStyle} dir="ltr">
520522
{tokens
521523
.map((line, index) => {

apps/webapp/app/components/code/TSQLEditor.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { sql, StandardSQL } from "@codemirror/lang-sql";
22
import { autocompletion, startCompletion } from "@codemirror/autocomplete";
33
import { linter, lintGutter } from "@codemirror/lint";
4-
import { EditorView } from "@codemirror/view";
4+
import { EditorView, keymap } from "@codemirror/view";
55
import type { ViewUpdate } from "@codemirror/view";
66
import { CheckIcon, ClipboardIcon, SparklesIcon, TrashIcon } from "@heroicons/react/20/solid";
77
import {
@@ -60,6 +60,32 @@ const defaultProps: TSQLEditorDefaultProps = {
6060
schema: [],
6161
};
6262

63+
// Toggle comment on current line with -- comment symbol
64+
const toggleLineComment = (view: EditorView): boolean => {
65+
const { from } = view.state.selection.main;
66+
const line = view.state.doc.lineAt(from);
67+
const lineText = line.text;
68+
const trimmed = lineText.trimStart();
69+
const indent = lineText.length - trimmed.length;
70+
71+
if (trimmed.startsWith("--")) {
72+
// Remove comment: strip "-- " or just "--"
73+
const afterComment = trimmed.slice(2);
74+
const newText = lineText.slice(0, indent) + afterComment.replace(/^\s/, "");
75+
view.dispatch({
76+
changes: { from: line.from, to: line.to, insert: newText },
77+
});
78+
} else {
79+
// Add comment: prepend "-- " to the line content
80+
const newText = lineText.slice(0, indent) + "-- " + trimmed;
81+
view.dispatch({
82+
changes: { from: line.from, to: line.to, insert: newText },
83+
});
84+
}
85+
86+
return true;
87+
};
88+
6389
export function TSQLEditor(opts: TSQLEditorProps) {
6490
const {
6591
defaultValue = "",
@@ -133,6 +159,14 @@ export function TSQLEditor(opts: TSQLEditorProps) {
133159
);
134160
}
135161

162+
// Add keyboard shortcut for toggling comments
163+
exts.push(
164+
keymap.of([
165+
{ key: "Cmd-/", run: toggleLineComment },
166+
{ key: "Ctrl-/", run: toggleLineComment },
167+
])
168+
);
169+
136170
return exts;
137171
}, [schema, linterEnabled]);
138172

@@ -218,6 +252,9 @@ export function TSQLEditor(opts: TSQLEditorProps) {
218252
"min-h-0 flex-1 overflow-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
219253
)}
220254
ref={editor}
255+
onClick={() => {
256+
view?.focus();
257+
}}
221258
onBlur={() => {
222259
if (!onBlur) return;
223260
if (!view) return;

apps/webapp/app/components/code/codeMirrorSetup.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { closeBrackets } from "@codemirror/autocomplete";
2-
import { indentWithTab } from "@codemirror/commands";
2+
import { indentWithTab, history, historyKeymap, undo, redo } from "@codemirror/commands";
33
import { bracketMatching } from "@codemirror/language";
44
import { lintKeymap } from "@codemirror/lint";
55
import { highlightSelectionMatches } from "@codemirror/search";
@@ -18,6 +18,7 @@ export function getEditorSetup(showLineNumbers = true, showHighlights = true): A
1818
const options = [
1919
drawSelection(),
2020
dropCursor(),
21+
history(),
2122
bracketMatching(),
2223
closeBrackets(),
2324
Prec.highest(
@@ -31,7 +32,15 @@ export function getEditorSetup(showLineNumbers = true, showHighlights = true): A
3132
},
3233
])
3334
),
34-
keymap.of([indentWithTab, ...lintKeymap]),
35+
// Explicit undo/redo keybindings with high precedence
36+
Prec.high(
37+
keymap.of([
38+
{ key: "Mod-z", run: undo },
39+
{ key: "Mod-Shift-z", run: redo },
40+
{ key: "Mod-y", run: redo },
41+
])
42+
),
43+
keymap.of([indentWithTab, ...historyKeymap, ...lintKeymap]),
3544
];
3645

3746
if (showLineNumbers) {

apps/webapp/app/components/logs/LogDetailView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useEffect, useState } from "react";
88
import { useTypedFetcher } from "remix-typedjson";
99
import { cn } from "~/utils/cn";
1010
import { Button } from "~/components/primitives/Buttons";
11-
import { DateTime } from "~/components/primitives/DateTime";
11+
import { DateTimeAccurate } from "~/components/primitives/DateTime";
1212
import { Header2, Header3 } from "~/components/primitives/Headers";
1313
import { Paragraph } from "~/components/primitives/Paragraph";
1414
import { Spinner } from "~/components/primitives/Spinner";
@@ -234,7 +234,7 @@ function DetailsTab({ log, runPath, searchTerm }: { log: LogEntry; runPath: stri
234234
<div className="mb-6">
235235
<Header3 className="mb-2">Timestamp</Header3>
236236
<div className="text-sm text-text-dimmed">
237-
<DateTime date={log.startTime} />
237+
<DateTimeAccurate date={log.startTime} />
238238
</div>
239239
</div>
240240

apps/webapp/app/components/logs/LogsTable.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ArrowPathIcon, ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid";
2+
import { Link } from "@remix-run/react";
23
import { useEffect, useRef, useState } from "react";
34
import { cn } from "~/utils/cn";
45
import { Button } from "~/components/primitives/Buttons";
@@ -8,7 +9,7 @@ import { useProject } from "~/hooks/useProject";
89
import type { LogEntry } from "~/presenters/v3/LogsListPresenter.server";
910
import { getLevelColor, highlightSearchText } from "~/utils/logUtils";
1011
import { v3RunSpanPath } from "~/utils/pathBuilder";
11-
import { DateTime } from "../primitives/DateTime";
12+
import { DateTimeAccurate } from "../primitives/DateTime";
1213
import { Paragraph } from "../primitives/Paragraph";
1314
import { Spinner } from "../primitives/Spinner";
1415
import { TruncatedCopyableValue } from "../primitives/TruncatedCopyableValue";
@@ -24,8 +25,6 @@ import {
2425
TableRow,
2526
type TableVariant,
2627
} from "../primitives/Table";
27-
import { PopoverMenuItem } from "~/components/primitives/Popover";
28-
import { Link } from "@remix-run/react";
2928

3029
type LogsTableProps = {
3130
logs: LogEntry[];
@@ -34,6 +33,7 @@ type LogsTableProps = {
3433
isLoadingMore?: boolean;
3534
hasMore?: boolean;
3635
onLoadMore?: () => void;
36+
onCheckForMore?: () => void;
3737
variant?: TableVariant;
3838
selectedLogId?: string;
3939
onLogSelect?: (logId: string) => void;
@@ -63,6 +63,7 @@ export function LogsTable({
6363
isLoadingMore = false,
6464
hasMore = false,
6565
onLoadMore,
66+
onCheckForMore,
6667
selectedLogId,
6768
onLogSelect,
6869
}: LogsTableProps) {
@@ -161,7 +162,7 @@ export function LogsTable({
161162
boxShadow: getLevelBoxShadow(log.level),
162163
}}
163164
>
164-
<DateTime date={log.startTime} />
165+
<DateTimeAccurate date={log.startTime} />
165166
</TableCell>
166167
<TableCell className="min-w-24">
167168
<TruncatedCopyableValue value={log.runId} />
@@ -213,11 +214,20 @@ export function LogsTable({
213214
</div>
214215
</div>
215216
)}
216-
{/* Show all logs message */}
217+
{/* Show all logs message with check for more button */}
217218
{!hasMore && logs.length > 0 && (
218219
<div className="flex items-center justify-center py-12">
219-
<div className="flex items-center gap-2">
220+
<div className="flex flex-col items-center gap-3">
220221
<span className="text-text-dimmed">Showing all {logs.length} logs</span>
222+
{onCheckForMore && (
223+
<Button
224+
LeadingIcon={ArrowPathIcon}
225+
variant="tertiary/small"
226+
onClick={onCheckForMore}
227+
>
228+
Check for new logs
229+
</Button>
230+
)}
221231
</div>
222232
</div>
223233
)}

apps/webapp/app/components/primitives/DateTime.tsx

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { GlobeAltIcon, GlobeAmericasIcon } from "@heroicons/react/20/solid";
2+
import { useRouteLoaderData } from "@remix-run/react";
23
import { Laptop } from "lucide-react";
34
import { memo, type ReactNode, useMemo, useSyncExternalStore } from "react";
45
import { CopyButton } from "./CopyButton";
@@ -19,7 +20,7 @@ function getLocalTimeZone(): string {
1920
// For SSR compatibility: returns "UTC" on server, actual timezone on client
2021
function subscribeToTimeZone() {
2122
// No-op - timezone doesn't change
22-
return () => { };
23+
return () => {};
2324
}
2425

2526
function getTimeZoneSnapshot(): string {
@@ -39,6 +40,18 @@ export function useLocalTimeZone(): string {
3940
return useSyncExternalStore(subscribeToTimeZone, getTimeZoneSnapshot, getServerTimeZoneSnapshot);
4041
}
4142

43+
/**
44+
* Hook to get the user's preferred timezone.
45+
* Returns the timezone stored in the user's preferences cookie (from root loader),
46+
* falling back to the browser's local timezone if not set.
47+
*/
48+
export function useUserTimeZone(): string {
49+
const rootData = useRouteLoaderData("root") as { timezone?: string } | undefined;
50+
const localTimeZone = useLocalTimeZone();
51+
// Use stored timezone from cookie, or fall back to browser's local timezone
52+
return rootData?.timezone && rootData.timezone !== "UTC" ? rootData.timezone : localTimeZone;
53+
}
54+
4255
type DateTimeProps = {
4356
date: Date | string;
4457
timeZone?: string;
@@ -63,15 +76,15 @@ export const DateTime = ({
6376
hour12 = true,
6477
}: DateTimeProps) => {
6578
const locales = useLocales();
66-
const localTimeZone = useLocalTimeZone();
79+
const userTimeZone = useUserTimeZone();
6780

6881
const realDate = useMemo(() => (typeof date === "string" ? new Date(date) : date), [date]);
6982

7083
const formattedDateTime = (
7184
<span suppressHydrationWarning>
7285
{formatDateTime(
7386
realDate,
74-
timeZone ?? localTimeZone,
87+
timeZone ?? userTimeZone,
7588
locales,
7689
includeSeconds,
7790
includeTime,
@@ -91,7 +104,7 @@ export const DateTime = ({
91104
<TooltipContent
92105
realDate={realDate}
93106
timeZone={timeZone}
94-
localTimeZone={localTimeZone}
107+
localTimeZone={userTimeZone}
95108
locales={locales}
96109
/>
97110
}
@@ -167,7 +180,7 @@ export function formatDateTimeISO(date: Date, timeZone: string): string {
167180
// New component that only shows date when it changes
168181
export const SmartDateTime = ({ date, previousDate = null, hour12 = true }: DateTimeProps) => {
169182
const locales = useLocales();
170-
const localTimeZone = useLocalTimeZone();
183+
const userTimeZone = useUserTimeZone();
171184
const realDate = typeof date === "string" ? new Date(date) : date;
172185
const realPrevDate = previousDate
173186
? typeof previousDate === "string"
@@ -180,8 +193,8 @@ export const SmartDateTime = ({ date, previousDate = null, hour12 = true }: Date
180193

181194
// Format with appropriate function
182195
const formattedDateTime = showDatePart
183-
? formatSmartDateTime(realDate, localTimeZone, locales, hour12)
184-
: formatTimeOnly(realDate, localTimeZone, locales, hour12);
196+
? formatSmartDateTime(realDate, userTimeZone, locales, hour12)
197+
: formatTimeOnly(realDate, userTimeZone, locales, hour12);
185198

186199
return <span suppressHydrationWarning>{formattedDateTime.replace(/\s/g, String.fromCharCode(32))}</span>;
187200
};
@@ -235,14 +248,16 @@ function formatTimeOnly(
235248

236249
const DateTimeAccurateInner = ({
237250
date,
238-
timeZone = "UTC",
251+
timeZone,
239252
previousDate = null,
240253
showTooltip = true,
241254
hideDate = false,
242255
hour12 = true,
243256
}: DateTimeProps) => {
244257
const locales = useLocales();
245-
const localTimeZone = useLocalTimeZone();
258+
const userTimeZone = useUserTimeZone();
259+
// Use provided timeZone prop if available, otherwise fall back to user's preferred timezone
260+
const displayTimeZone = timeZone ?? userTimeZone;
246261
const realDate = typeof date === "string" ? new Date(date) : date;
247262
const realPrevDate = previousDate
248263
? typeof previousDate === "string"
@@ -253,13 +268,13 @@ const DateTimeAccurateInner = ({
253268
// Smart formatting based on whether date changed
254269
const formattedDateTime = useMemo(() => {
255270
return hideDate
256-
? formatTimeOnly(realDate, localTimeZone, locales, hour12)
271+
? formatTimeOnly(realDate, displayTimeZone, locales, hour12)
257272
: realPrevDate
258273
? isSameDay(realDate, realPrevDate)
259-
? formatTimeOnly(realDate, localTimeZone, locales, hour12)
260-
: formatDateTimeAccurate(realDate, localTimeZone, locales, hour12)
261-
: formatDateTimeAccurate(realDate, localTimeZone, locales, hour12);
262-
}, [realDate, localTimeZone, locales, hour12, hideDate, previousDate]);
274+
? formatTimeOnly(realDate, displayTimeZone, locales, hour12)
275+
: formatDateTimeAccurate(realDate, displayTimeZone, locales, hour12)
276+
: formatDateTimeAccurate(realDate, displayTimeZone, locales, hour12);
277+
}, [realDate, displayTimeZone, locales, hour12, hideDate, previousDate]);
263278

264279
if (!showTooltip)
265280
return <span suppressHydrationWarning>{formattedDateTime.replace(/\s/g, String.fromCharCode(32))}</span>;
@@ -268,7 +283,7 @@ const DateTimeAccurateInner = ({
268283
<TooltipContent
269284
realDate={realDate}
270285
timeZone={timeZone}
271-
localTimeZone={localTimeZone}
286+
localTimeZone={userTimeZone}
272287
locales={locales}
273288
/>
274289
);
@@ -328,9 +343,9 @@ function formatDateTimeAccurate(
328343

329344
export const DateTimeShort = ({ date, hour12 = true }: DateTimeProps) => {
330345
const locales = useLocales();
331-
const localTimeZone = useLocalTimeZone();
346+
const userTimeZone = useUserTimeZone();
332347
const realDate = typeof date === "string" ? new Date(date) : date;
333-
const formattedDateTime = formatDateTimeShort(realDate, localTimeZone, locales, hour12);
348+
const formattedDateTime = formatDateTimeShort(realDate, userTimeZone, locales, hour12);
334349

335350
return <span suppressHydrationWarning>{formattedDateTime.replace(/\s/g, String.fromCharCode(32))}</span>;
336351
};

0 commit comments

Comments
 (0)