diff --git a/site/src/components/Filter/filter.tsx b/site/src/components/Filter/filter.tsx index c16bd9f8fe339..3e85c0dd42c71 100644 --- a/site/src/components/Filter/filter.tsx +++ b/site/src/components/Filter/filter.tsx @@ -23,6 +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 Divider from "@mui/material/Divider" +import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined" + +export type PresetFilter = { + name: string + query: string +} type FilterValues = Record @@ -127,12 +134,16 @@ export const Filter = ({ error, skeleton, options, + learnMoreLink, + presets, }: { filter: ReturnType skeleton: ReactNode isLoading: boolean + learnMoreLink: string error?: unknown options?: ReactNode + presets: PresetFilter[] }) => { const shouldDisplayError = hasError(error) && isApiValidationError(error) const hasFilterQuery = filter.query !== "" @@ -148,54 +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) + }, + 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} )} @@ -203,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 dc1103396beb9..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,8 @@ 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 f134ba5746bb3..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,9 +44,11 @@ export const WorkspacesFilter = ({ }) => { return ( {menus.user && } diff --git a/site/src/pages/WorkspacesPage/filter/menus.ts b/site/src/pages/WorkspacesPage/filter/menus.ts index eeaa5316bf984..fa5719c219859 100644 --- a/site/src/pages/WorkspacesPage/filter/menus.ts +++ b/site/src/pages/WorkspacesPage/filter/menus.ts @@ -29,6 +29,14 @@ export const useUserFilterMenu = ({ value, id: "owner", getSelectedOption: async () => { + 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) {