Skip to content

Commit

Permalink
Merge branch 'dev' of github.com:msqd/harp into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
hartym committed Jun 6, 2024
2 parents c03edd1 + 1cfb81c commit c846665
Show file tree
Hide file tree
Showing 28 changed files with 220 additions and 26 deletions.
3 changes: 3 additions & 0 deletions harp/config/factories/tests/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ def test_add_application():
new_config = Config.deserialize(serialized)
assert new_config == config

new_config.add_application("foo.bar")

# Try adding twice
new_config.add_application("foo.bar")
assert new_config != config
assert new_config.settings == {
Expand Down
2 changes: 2 additions & 0 deletions harp_apps/dashboard/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@
"@sentry/browser": "^7.116.0",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.13",
"@types/lodash": "^4.17.4",
"date-fns": "^3.6.0",
"http-status-codes": "^2.3.0",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"match-sorter": "^6.3.4",
"prismjs": "^1.29.0",
"prop-types": "^15.8.1",
Expand Down
14 changes: 10 additions & 4 deletions harp_apps/dashboard/frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { QueryObserverSuccessResult } from "react-query/types/core/types"

interface OnQuerySuccessCommonProps<T> {
children: (...queries: QueryObserverSuccessResult<T>[]) => ReactElement
onQueryError?: () => void
}

interface OnQuerySuccessProps<T> extends OnQuerySuccessCommonProps<T> {
Expand Down Expand Up @@ -32,6 +33,12 @@ function OnQuerySuccess<T>(props: OnQuerySuccessProps<T> | OnQueriesSuccessProps

const { showBoundary } = useErrorBoundary()

if (anyQueryIsError(queries)) {
if (props?.onQueryError) {
props.onQueryError()
}
}

if (anyQueryIsLoading(queries)) {
return <div>Loading...</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Filters } from "Types/filters"
import { renderWithClient } from "tests/utils"

import { FiltersSidebar } from "./FiltersSidebar"
import { MemoryRouter } from "react-router-dom"

const filters: Filters = {}

Expand All @@ -14,8 +15,11 @@ const setFilters = (filters: Filters) => {
}
void describe("FiltersSidebar", () => {
it("renders the title and data when the query is successful", async () => {
const result = renderWithClient(<FiltersSidebar filters={filters} setFilters={setFilters} />)

const result = renderWithClient(
<MemoryRouter>
<FiltersSidebar filters={filters} setFilters={setFilters} />
</MemoryRouter>,
)
await result.findByText("Request Method")
expect(result.container).toMatchSnapshot()
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { useCallback, useEffect } from "react"
import { useLocation, useNavigate } from "react-router-dom"

import { useTransactionsFiltersQuery } from "Domain/Transactions"
import { Filter, Filters } from "Types/filters"
import { Pane } from "ui/Components/Pane"
Expand All @@ -21,17 +24,70 @@ interface FiltersSidebarProps {
}

export function FiltersSidebar({ filters, setFilters }: FiltersSidebarProps) {
const location = useLocation()
const navigate = useNavigate()

const filtersQuery = useTransactionsFiltersQuery()

const _createSetFilterFor = (name: string) => (value: Filter) => {
setFilters({ ...filters, [name]: value })
}
const _createSetFilterFor = (name: string) =>
useCallback(
(value: Filter) => {
setFilters({ ...filters, [name]: value })
},
[name],
)

const setEndpointFilter = _createSetFilterFor("endpoint")
const setMethodFilter = _createSetFilterFor("method")
const setStatusFilter = _createSetFilterFor("status")
const setFlagsFilter = _createSetFilterFor("flag")

// Set filters from query parameters
useEffect(() => {
const filtersMap = {
endpoint: setEndpointFilter,
method: setMethodFilter,
status: setStatusFilter,
flag: setFlagsFilter,
}
const queryParams = new URLSearchParams(location.search)
const updateFilter = (name: string, setFunction: (value: Filter) => void) => {
const values = queryParams.getAll(name)
if (values.length) {
setFunction(values)
}
}

for (const [name, setFunction] of Object.entries(filtersMap)) {
updateFilter(name, setFunction)
}
}, [location.search, setEndpointFilter, setMethodFilter, setStatusFilter, setFlagsFilter])

// Update query parameters from filters
useEffect(() => {
const queryParams = new URLSearchParams(location.search)

const updateQueryParams = (filterName: string) => {
if (!filters[filterName] || !filters[filterName]?.length) {
queryParams.delete(filterName)
} else {
// Delete existing query parameters for the filter
queryParams.delete(filterName)

// Append new query parameters for the filter
filters[filterName]?.forEach((value) => queryParams.append(filterName, String(value)))
}
}

// Update query parameters for each filter
Object.keys(filters).forEach(updateQueryParams)

navigate({
pathname: location.pathname,
search: queryParams.toString(),
})
}, [filters, location.pathname, location.search, navigate])

return (
<Pane hasDefaultPadding={false} className="divide-y divide-gray-100 overflow-hidden text-gray-900 sm:text-sm">
{/* TODO implement search
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from "react"
import { QueryObserverSuccessResult } from "react-query/types/core/types"
import { useLocation, useNavigate } from "react-router-dom"

import { OnQuerySuccess } from "Components/Utilities/OnQuerySuccess.tsx"
import { ItemList } from "Domain/Api/Types"
Expand All @@ -21,10 +22,28 @@ export function TransactionListOnQuerySuccess({
filters: Filters
setFilters: (filters: Filters) => void
}) {
const location = useLocation()
const navigate = useNavigate()
const queryParams = new URLSearchParams(location.search)

const [selected, setSelected] = useState<Transaction | null>(null)
const hasSelection = selected && selected.id
const selectedId = selected?.id || queryParams.get("selected")
const hasSelection = !!selectedId
const [isFiltersOpen, setIsFiltersOpen] = useState(true)
const detailQuery = useTransactionsDetailQuery(selected?.id)
const detailQuery = useTransactionsDetailQuery(selected?.id || selectedId!)

const updateQueryParam = (paramName: string, paramValue: string | undefined) => {
if (paramValue) {
queryParams.set(paramName, paramValue)
} else {
queryParams.delete(paramName)
}

navigate({
pathname: location.pathname,
search: queryParams.toString(),
})
}

return (
<div className="flex w-full items-start gap-x-8 relative">
Expand All @@ -43,22 +62,29 @@ export function TransactionListOnQuerySuccess({
<TransactionDataTable
transactions={query.data.items}
onSelectionChange={(newSelected) =>
newSelected && newSelected.id && (!hasSelection || selected.id != newSelected.id)
? setSelected(newSelected)
: setSelected(null)
newSelected && newSelected.id && (!hasSelection || selected?.id != newSelected.id)
? (setSelected(newSelected), updateQueryParam("selected", newSelected.id))
: (setSelected(null), updateQueryParam("selected", undefined))
}
selected={hasSelection ? selected : undefined}
selected={selected ? selected : undefined}
/>
</main>

{hasSelection ? (
<aside className="sticky top-8 w-2/5 min-w-96 shrink-0 block">
<div className="text-right">
<OpenInNewWindowLink id={selected.id!} />
<DetailsCloseButton onClick={() => setSelected(null)} />
<OpenInNewWindowLink id={selectedId} />
<DetailsCloseButton onClick={() => (setSelected(null), updateQueryParam("selected", undefined))} />
</div>
<OnQuerySuccess query={detailQuery}>
{(query) => <TransactionDetailOnQuerySuccess query={query} />}

<OnQuerySuccess
query={detailQuery}
onQueryError={() => (setSelected(null), updateQueryParam("selected", undefined))}
>
{(query) => {
setSelected(query.data)
return <TransactionDetailOnQuerySuccess query={query} />
}}
</OnQuerySuccess>
</aside>
) : null}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,75 @@
import { useEffect, useState } from "react"
import { isEqual } from "lodash"
import { useEffect, useMemo, useRef, useState } from "react"
import { useLocation, useNavigate } from "react-router-dom"

import { Page } from "Components/Page"
import { PageTitle } from "Components/Page/PageTitle.tsx"
import { OnQuerySuccess } from "Components/Utilities/OnQuerySuccess"
import { useTransactionsListQuery } from "Domain/Transactions"
import { Filters } from "Types/filters"
import { SearchBar } from "ui/Components/SearchBar/SearchBar.tsx"
import { SearchBar } from "ui/Components/SearchBar/SearchBar"

import { OptionalPaginator } from "./Components/OptionalPaginator.tsx"
import { TransactionListOnQuerySuccess } from "./TransactionListOnQuerySuccess.tsx"

export function TransactionListPage() {
const location = useLocation()

const navigate = useNavigate()

const queryParams = useMemo(() => new URLSearchParams(location.search), [location.search])

const [filters, setFilters] = useState<Filters>({})
const [page, setPage] = useState(1)
const [cursor, setCursor] = useState<string | undefined>(undefined)
const [search, setSearch] = useState<string | undefined>(undefined)

const [page, setPage] = useState(Number(queryParams.get("page")) || 1)
const [cursor, setCursor] = useState<string | undefined>(queryParams.get("cursor") || undefined)
const [search, setSearch] = useState<string | undefined>(queryParams.get("search") || undefined)
const query = useTransactionsListQuery({ filters, page, cursor, search })

// Keep refs of filters and search to reset page when a change is detected
const prevSearchRef = useRef<string | undefined>()
const prevFiltersRef = useRef<Filters>({})

useEffect(() => {
const queryParamsToUpdate = { page: page.toString(), cursor: cursor, search: search }
const updateQueryParam = (paramName: string, paramValue: string | undefined) => {
if (paramValue) {
queryParams.set(paramName, paramValue)
} else {
queryParams.delete(paramName)
}

navigate({
pathname: location.pathname,
search: queryParams.toString(),
})
}

for (const [key, value] of Object.entries(queryParamsToUpdate)) {
updateQueryParam(key, value)
}
}, [location.pathname, navigate, queryParams, search, cursor, page])

useEffect(() => {
if (page == 1 && query.isSuccess && query.data.items.length) {
setCursor(query.data.items[0].id)
}
}, [page, query])

useEffect(() => {
if (!isEqual(filters, prevFiltersRef.current)) {
setPage((prevPage) => (prevPage > 1 ? 1 : prevPage))
prevFiltersRef.current = filters
}
}, [filters])

useEffect(() => {
if (search !== prevSearchRef.current) {
setPage((prevPage) => (prevPage > 1 ? 1 : prevPage))
prevSearchRef.current = search
}
}, [search])

return (
<Page
title={
Expand All @@ -32,6 +79,7 @@ export function TransactionListPage() {
placeHolder="Search transactions"
setSearch={setSearch}
className="w-96 order-last lg:order-first pr-6"
search={search}
/>
{query.isSuccess ? (
<OptionalPaginator
Expand All @@ -48,7 +96,6 @@ export function TransactionListPage() {
</PageTitle>
}
>
{/* <SearchBar placeHolder="Search transactions" setSearch={setSearch} className="w-1/2" /> */}
<OnQuerySuccess query={query}>
{(query) => <TransactionListOnQuerySuccess query={query} filters={filters} setFilters={setFilters} />}
</OnQuerySuccess>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ exports[`renders well when the query is successful 1`] = `
name="search"
placeholder="Search transactions"
type="text"
value=""
/>
<div
class="inset-y-0 right-0 flex py-1.5 pr-1.5 hover:cursor-pointer"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ interface SearchBarProps {
placeHolder?: string
setSearch?: (value: string) => void
className?: string
search?: string
}

export const SearchBar = ({ label, setSearch, className, placeHolder }: SearchBarProps) => {
export const SearchBar = ({ label, setSearch, className, placeHolder, search }: SearchBarProps) => {
const inputRef = useRef<HTMLInputElement>(null)

const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -42,6 +43,7 @@ export const SearchBar = ({ label, setSearch, className, placeHolder }: SearchBa
id="search"
placeholder={placeHolder}
onKeyDown={handleKeyPress}
defaultValue={search}
className="overflow-ellipsis w-full !border-0 !p-0 focus:!ring-0 !ml-1"
/>
<div onClick={handleSearchClick} className="inset-y-0 right-0 flex py-1.5 pr-1.5 hover:cursor-pointer">
Expand Down
Loading

0 comments on commit c846665

Please sign in to comment.