Skip to content

Commit

Permalink
feat: Filter panel improvements (#447)
Browse files Browse the repository at this point in the history
ernestii authored Jul 1, 2024
1 parent 3be5652 commit fc07472
Showing 7 changed files with 244 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-hotels-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperdx/app': patch
---

Filter Panel improvements
82 changes: 73 additions & 9 deletions packages/app/src/SearchPage.components.tsx
Original file line number Diff line number Diff line change
@@ -7,9 +7,11 @@ import {
Card,
Checkbox,
Group,
Loader,
ScrollArea,
Stack,
Text,
TextInput,
UnstyledButton,
} from '@mantine/core';

@@ -78,8 +80,8 @@ export const TextButton = ({
label: React.ReactNode;
}) => {
return (
<UnstyledButton onClick={onClick}>
<Text size="xxs" c="gray.6" className={classes.textButton} lh={1}>
<UnstyledButton onClick={onClick} className={classes.textButton}>
<Text size="xxs" c="gray.6" lh={1}>
{label}
</Text>
</UnstyledButton>
@@ -125,6 +127,8 @@ type FilterGroupProps = {
onOnlyClick: (value: string) => void;
};

const MAX_FILTER_GROUP_ITEMS = 5;

export const FilterGroup = ({
name,
options,
@@ -134,6 +138,9 @@ export const FilterGroup = ({
onClearClick,
onOnlyClick,
}: FilterGroupProps) => {
const [search, setSearch] = React.useState('');
const [isExpanded, setExpanded] = React.useState(false);

const augmentedOptions = React.useMemo(() => {
return [
...Array.from(selectedValues)
@@ -143,19 +150,58 @@ export const FilterGroup = ({
];
}, [options, selectedValues]);

const displayedOptions = React.useMemo(() => {
if (search) {
return augmentedOptions.filter(option => {
return (
option.value &&
option.value.toLowerCase().includes(search.toLowerCase())
);
});
}
if (isExpanded) {
return augmentedOptions;
}
return augmentedOptions.slice(0, MAX_FILTER_GROUP_ITEMS);
}, [augmentedOptions, search, isExpanded]);

const showExpandButton =
!search && augmentedOptions.length > MAX_FILTER_GROUP_ITEMS;

return (
<Stack gap={6}>
<Stack gap={0}>
<Group justify="space-between">
<Text size="xs" c="dimmed">
{name}
</Text>
<TextInput
size="xs"
variant="unstyled"
placeholder={name}
leftSection={<Icon name="search" className="fs-8.5" />}
value={search}
w="60%"
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setSearch(event.currentTarget.value)
}
/>
{selectedValues.size > 0 && (
<TextButton label="Clear" onClick={onClearClick} />
<TextButton
label="Clear"
onClick={() => {
onClearClick();
setSearch('');
}}
/>
)}
</Group>
<Stack gap={0}>
{optionsLoading && <Text c="dimmed">Loading...</Text>}
{augmentedOptions.map(option => (
{optionsLoading && (
<Group m={6} gap="xs">
<Loader size={12} color="gray.6" />
<Text c="dimmed" size="xs">
Loading...
</Text>
</Group>
)}
{displayedOptions.map(option => (
<FilterCheckbox
key={option.value}
label={option.label}
@@ -164,6 +210,24 @@ export const FilterGroup = ({
onClickOnly={() => onOnlyClick(option.value)}
/>
))}
{showExpandButton && (
<div className="d-flex m-1">
<TextButton
label={
isExpanded ? (
<>
<Icon name="chevron-up" /> Less
</>
) : (
<>
<Icon name="chevron-down" /> Show more
</>
)
}
onClick={() => setExpanded(!isExpanded)}
/>
</div>
)}
</Stack>
</Stack>
);
42 changes: 41 additions & 1 deletion packages/app/src/SearchPage.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { Stack } from '@mantine/core';

import { FilterCheckbox } from './SearchPage.components';
import { FilterCheckbox, FilterGroup } from './SearchPage.components';

const meta = {
title: 'SearchPage/Filters',
@@ -35,4 +35,44 @@ export const Default = () => {
);
};

export const Group = () => {
return (
<div style={{ width: 200 }}>
<FilterGroup
name="Level"
options={[
...Array.from({ length: 20 }).map((_, index) => ({
value: `level${index}`,
label: `Level ${index}`,
})),
{
value: 'very-long-super-long-absolutely-ridiculously-long',
label: 'very-long-super-long-absolutely-ridiculously-long',
},
]}
selectedValues={new Set(['info'])}
onChange={() => {}}
onClearClick={() => {}}
onOnlyClick={() => {}}
/>
</div>
);
};

export const GroupLoading = () => {
return (
<div style={{ width: 200 }}>
<FilterGroup
name="Level"
options={[]}
optionsLoading
selectedValues={new Set(['info'])}
onChange={() => {}}
onClearClick={() => {}}
onOnlyClick={() => {}}
/>
</div>
);
};

export default meta;
24 changes: 8 additions & 16 deletions packages/app/src/SearchPage.tsx
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@ import { useRouter } from 'next/router';
import cx from 'classnames';
import { clamp, sub } from 'date-fns';
import { Button } from 'react-bootstrap';
import { ErrorBoundary } from 'react-error-boundary';
import { useHotkeys } from 'react-hotkeys-hook';
import {
Bar,
@@ -34,6 +33,7 @@ import {
import { ActionIcon, Indicator } from '@mantine/core';
import { notifications } from '@mantine/notifications';

import { ErrorBoundary } from './components/ErrorBoundary';
import api from './api';
import CreateLogAlertModal from './CreateLogAlertModal';
import { withAppNav } from './layout';
@@ -315,17 +315,7 @@ const LogViewerContainer = memo(function LogViewerContainer({

return (
<>
<ErrorBoundary
onError={err => {
console.error(err);
}}
fallbackRender={() => (
<div className="text-danger px-2 py-1 m-2 fs-7 font-monospace bg-danger-transparent">
An error occurred while rendering the event details. Contact support
for more help.
</div>
)}
>
<ErrorBoundary message="An error occurred while rendering the event details. Contact support for more help.">
<LogSidePanel
key={openedLog?.id}
logId={openedLog?.id}
@@ -848,10 +838,12 @@ function SearchPage() {
height: '100%',
}}
>
<SearchPageFilters
searchQuery={searchedQuery}
onSearchQueryChange={handleSearchQueryChange}
/>
<ErrorBoundary message="Unable to render search filters">
<SearchPageFilters
searchQuery={searchedQuery}
onSearchQueryChange={handleSearchQueryChange}
/>
</ErrorBoundary>
<div className="d-flex flex-column flex-grow-1">
<div className="d-flex mx-4 mt-2 justify-content-between">
<div className="fs-8 text-muted">
42 changes: 42 additions & 0 deletions packages/app/src/components/ErrorBoundary.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Meta } from '@storybook/react';

import { ErrorBoundary } from './ErrorBoundary';

const meta: Meta = {
title: 'ErrorBoundary',
component: ErrorBoundary,
};

const BadComponent = () => {
throw new Error('Error message');
};

export const Default = () => (
<ErrorBoundary>
<BadComponent />
</ErrorBoundary>
);

export const WithRetry = () => (
<ErrorBoundary onRetry={() => {}}>
<BadComponent />
</ErrorBoundary>
);

export const WithMessage = () => (
<ErrorBoundary
onRetry={() => {}}
message="An error occurred while rendering the event details. Contact support
for more help."
>
<BadComponent />
</ErrorBoundary>
);

export const WithErrorMessage = () => (
<ErrorBoundary onRetry={() => {}} message="Don't panic" showErrorMessage>
<BadComponent />
</ErrorBoundary>
);

export default meta;
59 changes: 59 additions & 0 deletions packages/app/src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
import { Alert, Button, Stack, Text } from '@mantine/core';

import { Icon } from './Icon';

type ErrorBoundaryProps = {
children: React.ReactNode;
message?: string;
showErrorMessage?: boolean;
allowReset?: boolean;
onRetry?: () => void;
};

/**
* A `react-error-boundary` wrapper with a predefined fallback component
*/
export const ErrorBoundary = ({
children,
onRetry,
allowReset,
showErrorMessage,
message,
}: ErrorBoundaryProps) => {
const showRetry = allowReset || !!onRetry;

return (
<ReactErrorBoundary
onError={error => {
console.error(error);
}}
fallbackRender={({ error, resetErrorBoundary }) => (
<Alert
p="xs"
color="orange"
icon={<Icon name="info-circle-fill" />}
title={message || 'Something went wrong'}
>
{(showErrorMessage || showRetry) && (
<Stack align="flex-start" gap="xs">
{showErrorMessage && <Text size="xs">{error.message}</Text>}
{showRetry && (
<Button
onClick={onRetry || resetErrorBoundary}
size="compact-xs"
color="orange"
>
Retry
</Button>
)}
</Stack>
)}
</Alert>
)}
>
{children}
</ReactErrorBoundary>
);
};
16 changes: 16 additions & 0 deletions packages/app/styles/SearchPage.module.scss
Original file line number Diff line number Diff line change
@@ -27,12 +27,28 @@
cursor: pointer;
border-radius: 4px;

position: relative;

input {
opacity: 0.8;
}

.textButton {
position: absolute;
right: 0;
top: 0;
bottom: 0;
display: none;
z-index: 1;
backdrop-filter: blur(4px);
padding: 0 8px;
border-radius: 4px;
&:hover {
background-color: $slate-900;
}
&:active {
background-color: $slate-950;
}
}

&:hover {

0 comments on commit fc07472

Please sign in to comment.