Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add issues to request detail page #37

Merged
merged 11 commits into from
Jun 14, 2024
2 changes: 1 addition & 1 deletion api/scripts/seed-assets/mizu_logs.json

Large diffs are not rendered by default.

12 changes: 9 additions & 3 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:ci": "biome ci . && eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0 && tsc",
"lint:ci": "biome ci . && eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0 && tsc",
"format": "biome check . --write",
"preview": "vite preview",
"deploy": "npm run build && wrangler pages deploy dist"
Expand All @@ -24,22 +24,28 @@
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-table": "^8.17.3",
"@types/highlight-words-core": "^1.2.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
"date-fns": "^2.30.0",
"highlight-words-core": "^1.2.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-highlight-words": "^0.20.0",
"react-markdown": "^9.0.1",
"react-query": "^3.39.3",
"react-router-dom": "^6.23.1",
"stopword": "^3.0.1",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"vite-plugin-svgr": "^4.2.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20.12.12",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@types/stopword": "^2.0.3",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react-swc": "^3.5.0",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { QueryClientProvider, queryClient } from "@/queries";
import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
import Layout from "./Layout";
import { RequestDetailsPage } from "./pages/RequestDetailsPage/RequestDetailsPage";
import { RequestsPage } from "./pages/RequestsPage/Requests";
import { RequestsPage } from "./pages/RequestsPage/RequestsPage";

export function App() {
return (
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/ui/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ const TableRow = React.forwardRef<
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
"border-b transition-colors data-[state=selected]:bg-muted",
{ "hover:bg-muted/50": props.onClick },
{ "cursor-pointer": props.onClick },
className,
)}
{...props}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { useTimeAgo } from "./useTimeAgo.js";
export { useRequestDetails } from "./useRequestDetails.js";
export { useKeySequence } from "./keyboard.ts";
12 changes: 12 additions & 0 deletions frontend/src/hooks/useRequestDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useMizuTraces } from "@/queries";
import { isError } from "react-query";

export function useRequestDetails(traceId?: string) {
const query = useMizuTraces();
const trace = traceId ? query.data?.find((t) => t.id === traceId) : undefined;
return {
isLoading: query.isLoading,
isError: isError(query),
trace,
};
}
124 changes: 124 additions & 0 deletions frontend/src/hooks/useTimeAgo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import calculateDifferenceInMilliseconds from "date-fns/differenceInMilliseconds";
import format from "date-fns/format";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict";
import { useEffect, useRef, useState } from "react";

// In ms
const FIVE_SECONDS = 5000;
const TEN_SECONDS = 10_000;
const HALF_MINUTE = 30_000;
const MINUTE = 60_000; /* 60 * 1000ms */
const HOUR = 3_600_000; /* 60 * 60 * 1000ms */
const DAY = 86_400_000; /* 24 hours, 24 * 60 * 60 * 1000ms */

// By defining the intervals this way, we can determine which interval should
// be applied in a time window. This allows us to give more descriptive readable
// time strings as defined in the `date-fns` documentation:
// https://date-fns.org/v2.29.2/docs/formatDistanceToNow
const INTERVALS = {
[FIVE_SECONDS]: {
interval: FIVE_SECONDS,
nextInterval: HALF_MINUTE,
},
[HALF_MINUTE]: {
interval: TEN_SECONDS,
nextInterval: MINUTE,
},
[MINUTE]: {
interval: MINUTE,
nextInterval: HOUR,
},
[HOUR]: {
interval: HOUR,
nextInterval: DAY,
},
} as const;

/**
* Hook that parses the date string into a human-readable string that updates
* based on the right interval. If the comment is submitted >= 24hrs ago a
* string with that date will be returned that won't be updated.
*
* if the strict option is set to true the helpers like 'almost', 'over', 'less than' and the like
* aren't used.
*/
export function useTimeAgo(
date: string | undefined,
options?: { strict: boolean },
) {
const { strict = false } = options || {};

const interval = useRef<(typeof INTERVALS)[keyof typeof INTERVALS]>(
INTERVALS[FIVE_SECONDS],
);
const [formattedDate, setFormattedDate] = useState<string>();

useEffect(() => {
if (!date) {
return;
}

const parsedDate = new Date(date);

const getDifferenceInMilliseconds = () =>
calculateDifferenceInMilliseconds(new Date(), parsedDate);

const setFormatDate = () => {
const formatter = strict
? formatDistanceToNowStrict
: formatDistanceToNow;
const formattedDate = formatter(parsedDate, {
addSuffix: true,
includeSeconds: true,
});
setFormattedDate(formattedDate);
};

const setConstantDate = () => {
const constantDate = format(parsedDate, "LLLL d, yyyy");
setFormattedDate(constantDate);
};

// Prevent calling the timeout when we know >= 24hrs has passed & set a
// formatted post date string.
if (getDifferenceInMilliseconds() >= DAY) {
setConstantDate();
return;
}

let formatTimeout: ReturnType<typeof setTimeout>;
const setFormatTimeout = () => {
formatTimeout = setTimeout(() => {
const differenceInMilliSeconds = getDifferenceInMilliseconds();
// Prevent calling a new timeout when we know >= 24hrs has passed & set
// a formatted post date string.
if (differenceInMilliSeconds >= DAY) {
setConstantDate();
return;
}

if (differenceInMilliSeconds >= interval.current.nextInterval) {
if (interval.current.nextInterval === DAY) {
setConstantDate();
return;
}

interval.current = INTERVALS[interval.current.nextInterval];
}

setFormatDate();
setFormatTimeout();
}, interval.current.interval);
};

setFormatDate();
setFormatTimeout();

return () => {
clearTimeout(formatTimeout);
};
}, [date, strict]);

return formattedDate;
}
118 changes: 0 additions & 118 deletions frontend/src/pages/RequestDetailsPage/DataTable.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion frontend/src/pages/RequestDetailsPage/RequestDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ const InfoLog = ({ log }: { log: MizuLog }) => {
return (
<LogCard>
<LogDetailsHeader
eventName="console.log"
eventName={`console.${log.level}`}
traceId={log.traceId}
timestamp={log.timestamp}
description={""}
Expand Down
26 changes: 14 additions & 12 deletions frontend/src/pages/RequestDetailsPage/RequestDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,13 @@ import {
} from "@/components/ui/card";

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useMizuTraces } from "@/queries";
import { isError } from "react-query";

import { useRequestDetails } from "@/hooks";
import { useNavigate, useParams } from "react-router-dom";

import { useKeySequence } from "@/hooks";
import { TraceDetails } from "./RequestDetails";

function useRequestDetails(traceId?: string) {
const query = useMizuTraces();
const trace = traceId ? query.data?.find((t) => t.id === traceId) : undefined;
return {
isLoading: query.isLoading,
isError: isError(query),
trace,
};
}
import { RelatedIssueCounter, RelatedIssuesContent } from "./RequestIssues";

export function RequestDetailsPage() {
const { traceId } = useParams<{ traceId: string }>();
Expand All @@ -43,6 +34,14 @@ export function RequestDetailsPage() {
<TabsList>
<TabsTrigger value="all">All</TabsTrigger>
<TabsTrigger value="error">Errors</TabsTrigger>
<TabsTrigger value="issues" className="relative">
Issues
{traceId && (
<div className="absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white bg-red-500 border-2 border-white rounded-full -top-2 -end-2 dark:border-gray-900 empty:hidden">
<RelatedIssueCounter traceId={traceId} />
</div>
)}
</TabsTrigger>
</TabsList>
<div className="ml-auto flex items-center gap-2">
{/* TODO - Add global table action buttons? */}
Expand Down Expand Up @@ -95,6 +94,9 @@ export function RequestDetailsPage() {
</CardFooter>
</Card>
</TabsContent>
<TabsContent value="issues">
{traceId && <RelatedIssuesContent traceId={traceId} />}
</TabsContent>
</Tabs>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useRelevantIssues } from "@/queries/queries";

export function RelatedIssueCounter({ traceId }: { traceId: string }) {
const {
data: issues,
isLoading,
isLoadingError,
} = useRelevantIssues(traceId);

if (isLoading || isLoadingError) {
return null;
}

return (issues && issues.length) || null;
}
Loading
Loading