Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions apps/webapp/app/components/TimezoneSetter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useFetcher } from "@remix-run/react";
import { useEffect, useRef } from "react";
import { useTypedLoaderData } from "remix-typedjson";
import type { loader } from "~/root";

export function TimezoneSetter() {
const { timezone: storedTimezone } = useTypedLoaderData<typeof loader>();
const fetcher = useFetcher();
const hasSetTimezone = useRef(false);

useEffect(() => {
if (hasSetTimezone.current) return;

const browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;

if (browserTimezone && browserTimezone !== storedTimezone) {
hasSetTimezone.current = true;
fetcher.submit(
{ timezone: browserTimezone },
{
method: "POST",
action: "/resources/timezone",
encType: "application/json",
}
);
}
}, [storedTimezone, fetcher]);

return null;
}
39 changes: 38 additions & 1 deletion apps/webapp/app/components/code/TSQLEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { sql, StandardSQL } from "@codemirror/lang-sql";
import { autocompletion, startCompletion } from "@codemirror/autocomplete";
import { linter, lintGutter } from "@codemirror/lint";
import { EditorView } from "@codemirror/view";
import { EditorView, keymap } from "@codemirror/view";
import type { ViewUpdate } from "@codemirror/view";
import { CheckIcon, ClipboardIcon, SparklesIcon, TrashIcon } from "@heroicons/react/20/solid";
import {
Expand Down Expand Up @@ -60,6 +60,32 @@ const defaultProps: TSQLEditorDefaultProps = {
schema: [],
};

// Toggle comment on current line with -- comment symbol
const toggleLineComment = (view: EditorView): boolean => {
const { from } = view.state.selection.main;
const line = view.state.doc.lineAt(from);
const lineText = line.text;
const trimmed = lineText.trimStart();
const indent = lineText.length - trimmed.length;

if (trimmed.startsWith("--")) {
// Remove comment: strip "-- " or just "--"
const afterComment = trimmed.slice(2);
const newText = lineText.slice(0, indent) + afterComment.replace(/^\s/, "");
view.dispatch({
changes: { from: line.from, to: line.to, insert: newText },
});
} else {
// Add comment: prepend "-- " to the line content
const newText = lineText.slice(0, indent) + "-- " + trimmed;
view.dispatch({
changes: { from: line.from, to: line.to, insert: newText },
});
}

return true;
};

export function TSQLEditor(opts: TSQLEditorProps) {
const {
defaultValue = "",
Expand Down Expand Up @@ -133,6 +159,14 @@ export function TSQLEditor(opts: TSQLEditorProps) {
);
}

// Add keyboard shortcut for toggling comments
exts.push(
keymap.of([
{ key: "Cmd-/", run: toggleLineComment },
{ key: "Ctrl-/", run: toggleLineComment },
])
);

return exts;
}, [schema, linterEnabled]);

Expand Down Expand Up @@ -218,6 +252,9 @@ export function TSQLEditor(opts: TSQLEditorProps) {
"min-h-0 flex-1 overflow-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
)}
ref={editor}
onClick={() => {
view?.focus();
}}
onBlur={() => {
if (!onBlur) return;
if (!view) return;
Expand Down
13 changes: 11 additions & 2 deletions apps/webapp/app/components/code/codeMirrorSetup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { closeBrackets } from "@codemirror/autocomplete";
import { indentWithTab } from "@codemirror/commands";
import { indentWithTab, history, historyKeymap, undo, redo } from "@codemirror/commands";
import { bracketMatching } from "@codemirror/language";
import { lintKeymap } from "@codemirror/lint";
import { highlightSelectionMatches } from "@codemirror/search";
Expand All @@ -18,6 +18,7 @@ export function getEditorSetup(showLineNumbers = true, showHighlights = true): A
const options = [
drawSelection(),
dropCursor(),
history(),
bracketMatching(),
closeBrackets(),
Prec.highest(
Expand All @@ -31,7 +32,15 @@ export function getEditorSetup(showLineNumbers = true, showHighlights = true): A
},
])
),
keymap.of([indentWithTab, ...lintKeymap]),
// Explicit undo/redo keybindings with high precedence
Prec.high(
keymap.of([
{ key: "Mod-z", run: undo },
{ key: "Mod-Shift-z", run: redo },
{ key: "Mod-y", run: redo },
])
),
keymap.of([indentWithTab, ...historyKeymap, ...lintKeymap]),
];

if (showLineNumbers) {
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/app/components/logs/LogDetailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useEffect, useState } from "react";
import { useTypedFetcher } from "remix-typedjson";
import { cn } from "~/utils/cn";
import { Button } from "~/components/primitives/Buttons";
import { DateTime } from "~/components/primitives/DateTime";
import { DateTimeAccurate } from "~/components/primitives/DateTime";
import { Header2, Header3 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Spinner } from "~/components/primitives/Spinner";
Expand Down Expand Up @@ -234,7 +234,7 @@ function DetailsTab({ log, runPath, searchTerm }: { log: LogEntry; runPath: stri
<div className="mb-6">
<Header3 className="mb-2">Timestamp</Header3>
<div className="text-sm text-text-dimmed">
<DateTime date={log.startTime} />
<DateTimeAccurate date={log.startTime} />
</div>
</div>

Expand Down
30 changes: 18 additions & 12 deletions apps/webapp/app/components/logs/LogsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ArrowPathIcon, ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid";
import { Link } from "@remix-run/react";
import { useEffect, useRef, useState } from "react";
import { cn } from "~/utils/cn";
import { Button } from "~/components/primitives/Buttons";
Expand All @@ -8,7 +9,7 @@ import { useProject } from "~/hooks/useProject";
import type { LogEntry } from "~/presenters/v3/LogsListPresenter.server";
import { getLevelColor, highlightSearchText } from "~/utils/logUtils";
import { v3RunSpanPath } from "~/utils/pathBuilder";
import { DateTime } from "../primitives/DateTime";
import { DateTimeAccurate } from "../primitives/DateTime";
import { Paragraph } from "../primitives/Paragraph";
import { Spinner } from "../primitives/Spinner";
import { TruncatedCopyableValue } from "../primitives/TruncatedCopyableValue";
Expand All @@ -24,8 +25,6 @@ import {
TableRow,
type TableVariant,
} from "../primitives/Table";
import { PopoverMenuItem } from "~/components/primitives/Popover";
import { Link } from "@remix-run/react";

type LogsTableProps = {
logs: LogEntry[];
Expand All @@ -34,6 +33,7 @@ type LogsTableProps = {
isLoadingMore?: boolean;
hasMore?: boolean;
onLoadMore?: () => void;
onCheckForMore?: () => void;
variant?: TableVariant;
selectedLogId?: string;
onLogSelect?: (logId: string) => void;
Expand Down Expand Up @@ -63,6 +63,7 @@ export function LogsTable({
isLoadingMore = false,
hasMore = false,
onLoadMore,
onCheckForMore,
selectedLogId,
onLogSelect,
}: LogsTableProps) {
Expand Down Expand Up @@ -161,7 +162,7 @@ export function LogsTable({
boxShadow: getLevelBoxShadow(log.level),
}}
>
<DateTime date={log.startTime} />
<DateTimeAccurate date={log.startTime} />
</TableCell>
<TableCell className="min-w-24">
<TruncatedCopyableValue value={log.runId} />
Expand Down Expand Up @@ -203,21 +204,26 @@ export function LogsTable({
{/* Infinite scroll trigger */}
{hasMore && logs.length > 0 && (
<div ref={loadMoreRef} className="flex items-center justify-center py-12">
<div
className={cn(
"flex items-center gap-2",
!showLoadMoreSpinner && "invisible"
)}
>
<div className={cn("flex items-center gap-2", !showLoadMoreSpinner && "invisible")}>
<Spinner /> <span className="text-text-dimmed">Loading more…</span>
</div>
</div>
)}
{/* Show all logs message */}
{/* Show all logs message with check for more button */}
{!hasMore && logs.length > 0 && (
<div className="flex items-center justify-center py-12">
<div className="flex items-center gap-2">
<div className="flex flex-col items-center gap-3">
<span className="text-text-dimmed">Showing all {logs.length} logs</span>
{onCheckForMore && (
<Button
LeadingIcon={ArrowPathIcon}
variant="tertiary/small"
onClick={onCheckForMore}
disabled={isLoadingMore}
>
{isLoadingMore ? "Checking…" : "Check for new logs"}
</Button>
)}
</div>
</div>
)}
Expand Down
49 changes: 32 additions & 17 deletions apps/webapp/app/components/primitives/DateTime.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { GlobeAltIcon, GlobeAmericasIcon } from "@heroicons/react/20/solid";
import { useRouteLoaderData } from "@remix-run/react";
import { Laptop } from "lucide-react";
import { memo, type ReactNode, useMemo, useSyncExternalStore } from "react";
import { CopyButton } from "./CopyButton";
Expand All @@ -19,7 +20,7 @@ function getLocalTimeZone(): string {
// For SSR compatibility: returns "UTC" on server, actual timezone on client
function subscribeToTimeZone() {
// No-op - timezone doesn't change
return () => { };
return () => {};
}

function getTimeZoneSnapshot(): string {
Expand All @@ -39,6 +40,18 @@ export function useLocalTimeZone(): string {
return useSyncExternalStore(subscribeToTimeZone, getTimeZoneSnapshot, getServerTimeZoneSnapshot);
}

/**
* Hook to get the user's preferred timezone.
* Returns the timezone stored in the user's preferences cookie (from root loader),
* falling back to the browser's local timezone if not set.
*/
export function useUserTimeZone(): string {
const rootData = useRouteLoaderData("root") as { timezone?: string } | undefined;
const localTimeZone = useLocalTimeZone();
// Use stored timezone from cookie, or fall back to browser's local timezone
return rootData?.timezone && rootData.timezone !== "UTC" ? rootData.timezone : localTimeZone;
}

type DateTimeProps = {
date: Date | string;
timeZone?: string;
Expand All @@ -63,15 +76,15 @@ export const DateTime = ({
hour12 = true,
}: DateTimeProps) => {
const locales = useLocales();
const localTimeZone = useLocalTimeZone();
const userTimeZone = useUserTimeZone();

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

const formattedDateTime = (
<span suppressHydrationWarning>
{formatDateTime(
realDate,
timeZone ?? localTimeZone,
timeZone ?? userTimeZone,
locales,
includeSeconds,
includeTime,
Expand All @@ -91,7 +104,7 @@ export const DateTime = ({
<TooltipContent
realDate={realDate}
timeZone={timeZone}
localTimeZone={localTimeZone}
localTimeZone={userTimeZone}
locales={locales}
/>
}
Expand Down Expand Up @@ -167,7 +180,7 @@ export function formatDateTimeISO(date: Date, timeZone: string): string {
// New component that only shows date when it changes
export const SmartDateTime = ({ date, previousDate = null, hour12 = true }: DateTimeProps) => {
const locales = useLocales();
const localTimeZone = useLocalTimeZone();
const userTimeZone = useUserTimeZone();
const realDate = typeof date === "string" ? new Date(date) : date;
const realPrevDate = previousDate
? typeof previousDate === "string"
Expand All @@ -180,8 +193,8 @@ export const SmartDateTime = ({ date, previousDate = null, hour12 = true }: Date

// Format with appropriate function
const formattedDateTime = showDatePart
? formatSmartDateTime(realDate, localTimeZone, locales, hour12)
: formatTimeOnly(realDate, localTimeZone, locales, hour12);
? formatSmartDateTime(realDate, userTimeZone, locales, hour12)
: formatTimeOnly(realDate, userTimeZone, locales, hour12);

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

const DateTimeAccurateInner = ({
date,
timeZone = "UTC",
timeZone,
previousDate = null,
showTooltip = true,
hideDate = false,
hour12 = true,
}: DateTimeProps) => {
const locales = useLocales();
const localTimeZone = useLocalTimeZone();
const userTimeZone = useUserTimeZone();
// Use provided timeZone prop if available, otherwise fall back to user's preferred timezone
const displayTimeZone = timeZone ?? userTimeZone;
const realDate = typeof date === "string" ? new Date(date) : date;
const realPrevDate = previousDate
? typeof previousDate === "string"
Expand All @@ -253,13 +268,13 @@ const DateTimeAccurateInner = ({
// Smart formatting based on whether date changed
const formattedDateTime = useMemo(() => {
return hideDate
? formatTimeOnly(realDate, localTimeZone, locales, hour12)
? formatTimeOnly(realDate, displayTimeZone, locales, hour12)
: realPrevDate
? isSameDay(realDate, realPrevDate)
? formatTimeOnly(realDate, localTimeZone, locales, hour12)
: formatDateTimeAccurate(realDate, localTimeZone, locales, hour12)
: formatDateTimeAccurate(realDate, localTimeZone, locales, hour12);
}, [realDate, localTimeZone, locales, hour12, hideDate, previousDate]);
? formatTimeOnly(realDate, displayTimeZone, locales, hour12)
: formatDateTimeAccurate(realDate, displayTimeZone, locales, hour12)
: formatDateTimeAccurate(realDate, displayTimeZone, locales, hour12);
}, [realDate, displayTimeZone, locales, hour12, hideDate, previousDate]);

if (!showTooltip)
return <span suppressHydrationWarning>{formattedDateTime.replace(/\s/g, String.fromCharCode(32))}</span>;
Expand All @@ -268,7 +283,7 @@ const DateTimeAccurateInner = ({
<TooltipContent
realDate={realDate}
timeZone={timeZone}
localTimeZone={localTimeZone}
localTimeZone={userTimeZone}
locales={locales}
/>
);
Expand Down Expand Up @@ -328,9 +343,9 @@ function formatDateTimeAccurate(

export const DateTimeShort = ({ date, hour12 = true }: DateTimeProps) => {
const locales = useLocales();
const localTimeZone = useLocalTimeZone();
const userTimeZone = useUserTimeZone();
const realDate = typeof date === "string" ? new Date(date) : date;
const formattedDateTime = formatDateTimeShort(realDate, localTimeZone, locales, hour12);
const formattedDateTime = formatDateTimeShort(realDate, userTimeZone, locales, hour12);

return <span suppressHydrationWarning>{formattedDateTime.replace(/\s/g, String.fromCharCode(32))}</span>;
};
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/env.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1176,7 +1176,7 @@ const EnvironmentSchema = z
CLICKHOUSE_COMPRESSION_REQUEST: z.string().default("1"),

// Logs List Query Settings (for paginated log views)
CLICKHOUSE_LOGS_LIST_MAX_MEMORY_USAGE: z.coerce.number().int().default(256_000_000),
CLICKHOUSE_LOGS_LIST_MAX_MEMORY_USAGE: z.coerce.number().int().default(1_000_000_000),
CLICKHOUSE_LOGS_LIST_MAX_BYTES_BEFORE_EXTERNAL_SORT: z.coerce
.number()
.int()
Expand Down
Loading