Skip to content

Commit

Permalink
feat: transaction details ui
Browse files Browse the repository at this point in the history
  • Loading branch information
hartym committed Mar 22, 2024
1 parent 68267ef commit a4b0c38
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,28 @@ import { H4 } from "mkui/Components/Typography"

const Style = { ...BaseStyle }

function PrettyBody({ content = null, contentType = null }: { content: string | null; contentType: string | null }) {
export function PrettyBody({
content = null,
contentType = null,
}: {
content: string | null
contentType?: string | null
}) {
switch (contentType) {
case "application/json":
return (
<SyntaxHighlighter
language="javascript"
className="w-fit overflow-x-auto p-4 text-sm text-black language-javascript max-w-full"
className="w-fit overflow-x-auto text-black language-javascript max-w-full text-xs"
children={content || ""}
style={Style}
customStyle={{ fontSize: "0.9rem", padding: 0, border: 0 }}
customStyle={{ padding: 0, border: 0 }}
/>
)
}

return content ? (
<pre
className="max-w-full overflow-x-auto p-4 text-sm text-black"
style={{ fontSize: "0.8rem", padding: 0, border: 0 }}
>
<pre className="max-w-full overflow-x-auto p-4 text-xs text-black" style={{ padding: 0, border: 0 }}>
{content}
</pre>
) : null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const RequestHeading = ({
<ArrowRightIcon className="h-3 w-3 text-gray-500" aria-hidden="true" />
</span>
<RequestMethodBadge method={method} />
<span className="mx-1">{urlJoin("/", url || "")}</span>
<span className="mx-1 font-mono font-normal text-gray-500 text-xs">{urlJoin("/", url || "")}</span>
</div>
) : (
<span className="text-gray-500 text-sm font-normal">n/a</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ChevronDoubleLeftIcon, ChevronDoubleRightIcon } from "@heroicons/react/16/solid"

interface FiltersVisibilityButtonProps {
onClick: () => void
}

export function FiltersShowButton({ onClick }: FiltersVisibilityButtonProps) {
return (
<button onClick={onClick} className="text-gray-400 mx-1 font-medium text-xs">
<ChevronDoubleRightIcon className="h-3 w-3 inline-block" />
<div className="w-4">
<div className="rotate-90">filters</div>
</div>
</button>
)
}

export function FiltersHideButton({ onClick }: FiltersVisibilityButtonProps) {
return (
<button onClick={onClick} className="text-gray-400 mx-1 font-medium text-xs">
<ChevronDoubleLeftIcon className="h-3 w-3 inline-block" /> hide
</button>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ exports[`SystemPage > renders when the query is successful 1`] = `
GET
</span>
<span
class="mx-1"
class="mx-1 font-mono font-normal text-gray-500 text-xs"
>
/
</span>
Expand Down Expand Up @@ -212,8 +212,8 @@ exports[`SystemPage > renders when the query is successful 1`] = `
(application/json)
</h4>
<pre
class="w-fit overflow-x-auto p-4 text-sm text-black language-javascript max-w-full"
style="color: rgb(57, 58, 52); font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace; direction: ltr; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; font-size: 0.9rem; line-height: 1.2em; tab-size: 4; hyphens: none; padding: 0px; margin: .5em 0px; overflow: auto; border: 0px; background-color: white;"
class="w-fit overflow-x-auto text-black language-javascript max-w-full text-xs"
style="color: rgb(57, 58, 52); font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace; direction: ltr; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; font-size: .9em; line-height: 1.2em; tab-size: 4; hyphens: none; padding: 0px; margin: .5em 0px; overflow: auto; border: 0px; background-color: white;"
>
<code
class="language-javascript"
Expand Down
141 changes: 126 additions & 15 deletions frontend/src/Pages/Transactions/TransactionListPageOnQuerySuccess.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,112 @@
import { ChevronDoubleLeftIcon, ChevronDoubleRightIcon } from "@heroicons/react/16/solid"
import { useState } from "react"
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/outline"
import { ReactNode, useState } from "react"
import { QueryObserverSuccessResult } from "react-query/types/core/types"

import { RequestMethodBadge } from "Components/Badges/RequestMethodBadge.tsx"
import { OnQuerySuccess } from "Components/Utilities/OnQuerySuccess.tsx"
import { ItemList } from "Domain/Api/Types"
import { useTransactionsDetailQuery } from "Domain/Transactions"
import { Transaction } from "Models/Transaction"
import { Filters } from "Types/filters"
import { ucfirst } from "Utils/Strings.ts"
import { Pane } from "mkui/Components/Pane"
import { H5 } from "mkui/Components/Typography"

import { PrettyBody } from "./Components/Detail/TransactionMessagePanel.tsx"
import { FiltersHideButton, FiltersShowButton } from "./Components/FiltersVisibilityButtons.tsx"
import { TransactionDataTable } from "./Components/List"
import { FiltersSidebar } from "./Containers"
import { TransactionDetail } from "./Containers/Detail"

import { ResponseStatusBadge } from "../../Components/Badges/ResponseStatusBadge.tsx"
import { KeyValueSettings } from "../../Domain/System/useSystemSettingsQuery.ts"
import { useBlobQuery } from "../../Domain/Transactions/useBlobQuery.tsx"
import { SettingsTable } from "../System/Components"

interface FoldableProps {
open?: boolean
title: ReactNode
children: ReactNode
}

function Foldable({ open = true, title, children }: FoldableProps) {
const [isOpen, setIsOpen] = useState(open)

return (
<div className="px-4 py-3">
<H5 padding="pt-0" className="flex w-full cursor-pointer whitespace-nowrap" onClick={() => setIsOpen(!isOpen)}>
{/* title (clickable) */}
<div className="grow font-normal truncate">
<span className="font-semibold">{title}</span>
</div>

{/* control to fold/unfold the content */}
{isOpen ? (
<ChevronUpIcon className="h-4 w-4 min-w-4 text-gray-600" />
) : (
<ChevronDownIcon className="h-4 w-4 min-w-4 text-gray-600" />
)}
</H5>
{/* actual foldable content */}
<div className={"mt-2 space-y-2 overflow-x-auto " + (isOpen ? "" : "hidden")}>{children}</div>
</div>
)
}

function ShortMessageSummary({ kind, summary }: { kind: string; summary: string }) {
if (kind == "request") {
const [method, url] = summary.split(" ")
return (
<span className="font-normal">
<RequestMethodBadge method={method} /> <span className="text-gray-500 font-mono text-xs">{url}</span>
</span>
)
}

if (kind == "response") {
const splitSummary = summary.split(" ")
return (
<span className="font-normal">
<ResponseStatusBadge statusCode={parseInt(splitSummary[1])} />
</span>
)
}
}

function MessageHeaders({ id }: { id: string }) {
const query = useBlobQuery(id)

if (query && query.isSuccess && query.data !== undefined) {
return (
<table className="mb-2 w-full text-xs font-mono">
<tbody>
{query.data.content.split("\n").map((line, index) => {
const s = line.split(":", 2)
return (
<tr key={index}>
<td className="px-2 w-1 text-blue-600 truncate">{s[0]}</td>
<td className="px-2">{s[1]}</td>
</tr>
)
})}
</tbody>
</table>
)
}
}

function MessageBody({ id }: { id: string }) {
const query = useBlobQuery(id)

if (query && query.isSuccess && query.data !== undefined) {
return (
<div className="px-2">
<PrettyBody content={query.data.content} contentType={query.data.contentType} />
</div>
)
}

return null
}

export function TransactionListPageOnQuerySuccess({
query,
Expand All @@ -23,28 +119,20 @@ export function TransactionListPageOnQuerySuccess({
}) {
const [selected, setSelected] = useState<Transaction | null>(null)
const hasSelection = selected && selected.id

const [isFiltersOpen, setIsFiltersOpen] = useState(true)

const detailQuery = useTransactionsDetailQuery(selected?.id)

return (
<div className="flex w-full items-start gap-x-8 relative">
{isFiltersOpen ? (
<aside className="sticky top-8 hidden w-1/5 min-w-56 max-w-96 shrink-0 lg:block">
<div className="text-right">
<button onClick={() => setIsFiltersOpen(false)} className="text-gray-400 mx-1 font-medium text-xs">
<ChevronDoubleLeftIcon className="h-3 w-3 inline-block" /> hide
</button>
<FiltersHideButton onClick={() => setIsFiltersOpen(false)} />
</div>
<FiltersSidebar filters={filters} setFilters={setFilters} />
</aside>
) : (
<button onClick={() => setIsFiltersOpen(true)} className="text-gray-400 mx-1 font-medium text-xs">
<ChevronDoubleRightIcon className="h-3 w-3 inline-block" />
<div className="w-4">
<div className="rotate-90">filters</div>
</div>
</button>
<FiltersShowButton onClick={() => setIsFiltersOpen(true)} />
)}

<main className="flex-1 overflow-auto">
Expand All @@ -62,7 +150,30 @@ export function TransactionListPageOnQuerySuccess({
{hasSelection ? (
<aside className="sticky top-8 w-2/5 min-w-96 shrink-0 block">
<OnQuerySuccess query={detailQuery}>
{(query) => <TransactionDetail transaction={query.data} />}
{(query) => {
return (
<Pane
hasDefaultPadding={false}
className="divide-y divide-gray-100 overflow-hidden text-gray-900 sm:text-sm"
>
{(query.data.messages || []).map((message) => (
<Foldable
title={
<>
{ucfirst(message.kind)} <ShortMessageSummary kind={message.kind} summary={message.summary} />
</>
}
>
<MessageHeaders id={message.headers} />
<MessageBody id={message.body} />
</Foldable>
))}
<Foldable title="Raw" open={false}>
<SettingsTable settings={query.data as KeyValueSettings} />
</Foldable>
</Pane>
)
}}
</OnQuerySuccess>
</aside>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ exports[`renders well when the query is successful 1`] = `
GET
</span>
<span
class="mx-1"
class="mx-1 font-mono font-normal text-gray-500 text-xs"
>
/
</span>
Expand Down Expand Up @@ -302,7 +302,7 @@ exports[`renders well when the query is successful 1`] = `
GET
</span>
<span
class="mx-1"
class="mx-1 font-mono font-normal text-gray-500 text-xs"
>
/
</span>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/Utils/Strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ export function truncate(str: string, maxLength: number) {
}
return str.slice(0, maxLength) + "…"
}

export const ucfirst = (s: string) => (s && s[0].toUpperCase() + s.slice(1)) || ""
7 changes: 4 additions & 3 deletions frontend/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ export default {
fontFamily: {
sans: 'Lato, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
},
minWidth: {
"90%": "90%",
},
minWidth: {},
maxWidth: {
"1/5": "20%",
"1/3": "33%",
"2/5": "40%",
"90%": "90%",
128: "32rem",
},
Expand Down
2 changes: 0 additions & 2 deletions vendors/mkui/.ladle/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import GlobalStyles from "../src/Styles/GlobalStyles"

import "./index.css"

const ucfirst = (s: string) => (s && s[0].toUpperCase() + s.slice(1)) || ""

export const Provider: GlobalProvider = ({ children, globalState }) => {
return (
<>
Expand Down

0 comments on commit a4b0c38

Please sign in to comment.