From 204e8a42ac13ec725be364b3026706fa0b85f34f Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 6 Jun 2023 16:02:35 +0000 Subject: [PATCH 1/2] Add more info about advanced queries --- site/src/components/Filter/filter.tsx | 48 ++++++++++++++----- site/src/pages/UsersPage/UsersFilter.tsx | 1 + .../pages/WorkspacesPage/filter/filter.tsx | 1 + 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/site/src/components/Filter/filter.tsx b/site/src/components/Filter/filter.tsx index c16bd9f8fe339..16a9673a52ad6 100644 --- a/site/src/components/Filter/filter.tsx +++ b/site/src/components/Filter/filter.tsx @@ -23,6 +23,7 @@ import { BaseOption } from "./options" import debounce from "just-debounce-it" import MenuList from "@mui/material/MenuList" import { Loader } from "components/Loader/Loader" +import InfoOutlined from "@mui/icons-material/InfoOutlined" type FilterValues = Record @@ -127,10 +128,12 @@ export const Filter = ({ error, skeleton, options, + learnMoreLink, }: { filter: ReturnType skeleton: ReactNode isLoading: boolean + learnMoreLink: string error?: unknown options?: ReactNode }) => { @@ -168,6 +171,9 @@ export const Filter = ({ "& input::placeholder": { color: (theme) => theme.palette.text.secondary, }, + "& .MuiInputAdornment-root": { + marginLeft: 0, + }, }, startAdornment: ( @@ -179,19 +185,35 @@ export const Filter = ({ /> ), - endAdornment: hasFilterQuery && ( - - - { - filter.update("") - }} - > - - - - + endAdornment: ( + <> + {hasFilterQuery && ( + + + { + filter.update("") + }} + > + + + + + )} + + + + + + + + + ), }} /> diff --git a/site/src/pages/UsersPage/UsersFilter.tsx b/site/src/pages/UsersPage/UsersFilter.tsx index dc1103396beb9..4b9a5c7c960a2 100644 --- a/site/src/pages/UsersPage/UsersFilter.tsx +++ b/site/src/pages/UsersPage/UsersFilter.tsx @@ -49,6 +49,7 @@ export const UsersFilter = ({ }) => { return ( {menus.user && } From 18d7403e0cf8fe884045e5c078429832a1ad0600 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 6 Jun 2023 17:38:27 +0000 Subject: [PATCH 2/2] refactor(site): add presets back on filtering --- site/src/components/Filter/filter.tsx | 196 ++++++++++++------ site/src/pages/UsersPage/UsersFilter.tsx | 7 + .../pages/WorkspacesPage/WorkspacesPage.tsx | 5 +- .../WorkspacesPageView.stories.tsx | 2 +- .../pages/WorkspacesPage/filter/filter.tsx | 15 ++ site/src/pages/WorkspacesPage/filter/menus.ts | 8 + 6 files changed, 170 insertions(+), 63 deletions(-) diff --git a/site/src/components/Filter/filter.tsx b/site/src/components/Filter/filter.tsx index 16a9673a52ad6..3e85c0dd42c71 100644 --- a/site/src/components/Filter/filter.tsx +++ b/site/src/components/Filter/filter.tsx @@ -23,7 +23,13 @@ import { BaseOption } from "./options" import debounce from "just-debounce-it" import MenuList from "@mui/material/MenuList" import { Loader } from "components/Loader/Loader" -import InfoOutlined from "@mui/icons-material/InfoOutlined" +import Divider from "@mui/material/Divider" +import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined" + +export type PresetFilter = { + name: string + query: string +} type FilterValues = Record @@ -129,6 +135,7 @@ export const Filter = ({ skeleton, options, learnMoreLink, + presets, }: { filter: ReturnType skeleton: ReactNode @@ -136,6 +143,7 @@ export const Filter = ({ learnMoreLink: string error?: unknown options?: ReactNode + presets: PresetFilter[] }) => { const shouldDisplayError = hasError(error) && isApiValidationError(error) const hasFilterQuery = filter.query !== "" @@ -151,73 +159,71 @@ export const Filter = ({ skeleton ) : ( <> - { - setSearchQuery(e.target.value) - filter.debounceUpdate(e.target.value) - }, - sx: { - borderRadius: "6px", - "& input::placeholder": { - color: (theme) => theme.palette.text.secondary, + + filter.update(query)} + presets={presets} + learnMoreLink={learnMoreLink} + /> + { + setSearchQuery(e.target.value) + filter.debounceUpdate(e.target.value) }, - "& .MuiInputAdornment-root": { - marginLeft: 0, + sx: { + borderRadius: "6px", + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + marginLeft: "-1px", + "&:hover": { + zIndex: 2, + }, + "& input::placeholder": { + color: (theme) => theme.palette.text.secondary, + }, + "& .MuiInputAdornment-root": { + marginLeft: 0, + }, }, - }, - startAdornment: ( - - theme.palette.text.secondary, - }} - /> - - ), - endAdornment: ( - <> - {hasFilterQuery && ( - - - { - filter.update("") - }} - > - - - - - )} - + startAdornment: ( + + theme.palette.text.secondary, + }} + /> + + ), + endAdornment: hasFilterQuery && ( - + { + filter.update("") + }} > - + - - ), - }} - /> - + ), + }} + /> + {options} )} @@ -225,6 +231,78 @@ export const Filter = ({ ) } +const PresetMenu = ({ + presets, + learnMoreLink, + onSelect, +}: { + presets: PresetFilter[] + learnMoreLink: string + onSelect: (query: string) => void +}) => { + const [isOpen, setIsOpen] = useState(false) + const anchorRef = useRef(null) + + return ( + <> + + setIsOpen(false)} + anchorOrigin={{ + vertical: "bottom", + horizontal: "left", + }} + transformOrigin={{ + vertical: "top", + horizontal: "left", + }} + sx={{ "& .MuiMenu-paper": { py: 1 } }} + > + {presets.map((presetFilter) => ( + { + onSelect(presetFilter.query) + setIsOpen(false) + }} + > + {presetFilter.name} + + ))} + theme.palette.divider }} /> + { + setIsOpen(false) + }} + > + + View advanced filtering + + + + ) +} + export const FilterMenu = ({ id, menu, diff --git a/site/src/pages/UsersPage/UsersFilter.tsx b/site/src/pages/UsersPage/UsersFilter.tsx index 4b9a5c7c960a2..4bbc5b99a2ac9 100644 --- a/site/src/pages/UsersPage/UsersFilter.tsx +++ b/site/src/pages/UsersPage/UsersFilter.tsx @@ -11,6 +11,7 @@ import { } from "components/Filter/filter" import { BaseOption } from "components/Filter/options" import { UseFilterMenuOptions, useFilterMenu } from "components/Filter/menu" +import { userFilterQuery } from "utils/filters" type StatusOption = BaseOption & { color: string @@ -36,6 +37,11 @@ export const useStatusFilterMenu = ({ export type StatusFilterMenu = ReturnType +const PRESET_FILTERS = [ + { query: userFilterQuery.active, name: "Active users" }, + { query: userFilterQuery.all, name: "All users" }, +] + export const UsersFilter = ({ filter, error, @@ -49,6 +55,7 @@ export const UsersFilter = ({ }) => { return ( { - const me = useMe() const orgId = useOrganizationId() // If we use a useSearchParams for each hook, the values will not be in sync. // So we have to use a single one, centralizing the values, and pass it to @@ -23,7 +22,7 @@ const WorkspacesPage: FC = () => { const searchParamsResult = useSearchParams() const pagination = usePagination({ searchParamsResult }) const filter = useFilter({ - initialValue: `owner:${me.username}`, + initialValue: `owner:me`, searchParamsResult, onUpdate: () => { pagination.goToPage(1) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx index d2b154c2f014c..2c1663b4103d9 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx @@ -84,7 +84,7 @@ const mockMenu = { const defaultFilterProps = { filter: { - query: `owner:${MockUser.username}`, + query: `owner:me`, update: () => action("update"), debounceUpdate: action("debounce") as any, used: false, diff --git a/site/src/pages/WorkspacesPage/filter/filter.tsx b/site/src/pages/WorkspacesPage/filter/filter.tsx index 7c7d5243094ca..a6f6994278732 100644 --- a/site/src/pages/WorkspacesPage/filter/filter.tsx +++ b/site/src/pages/WorkspacesPage/filter/filter.tsx @@ -14,6 +14,20 @@ import { SearchFieldSkeleton, useFilter, } from "components/Filter/filter" +import { workspaceFilterQuery } from "utils/filters" + +const PRESET_FILTERS = [ + { query: workspaceFilterQuery.me, name: "My workspaces" }, + { query: workspaceFilterQuery.all, name: "All workspaces" }, + { + query: workspaceFilterQuery.running, + name: "Running workspaces", + }, + { + query: workspaceFilterQuery.failed, + name: "Failed workspaces", + }, +] export const WorkspacesFilter = ({ filter, @@ -30,6 +44,7 @@ export const WorkspacesFilter = ({ }) => { return ( { + if (value === "me") { + return { + label: me.username, + value: me.username, + avatarUrl: me.avatar_url, + } + } + const usersRes = await getUsers({ q: value, limit: 1 }) const firstUser = usersRes.users.at(0) if (firstUser && firstUser.username === value) {