mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore(site): remove new filter from experimental (#8112)
This commit is contained in:
Generated
+2
-4
@@ -7355,13 +7355,11 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"moons",
|
||||
"workspace_actions",
|
||||
"workspace_filter"
|
||||
"workspace_actions"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ExperimentMoons",
|
||||
"ExperimentWorkspaceActions",
|
||||
"ExperimentWorkspaceFilter"
|
||||
"ExperimentWorkspaceActions"
|
||||
]
|
||||
},
|
||||
"codersdk.Feature": {
|
||||
|
||||
Generated
+2
-6
@@ -6582,12 +6582,8 @@
|
||||
},
|
||||
"codersdk.Experiment": {
|
||||
"type": "string",
|
||||
"enum": ["moons", "workspace_actions", "workspace_filter"],
|
||||
"x-enum-varnames": [
|
||||
"ExperimentMoons",
|
||||
"ExperimentWorkspaceActions",
|
||||
"ExperimentWorkspaceFilter"
|
||||
]
|
||||
"enum": ["moons", "workspace_actions"],
|
||||
"x-enum-varnames": ["ExperimentMoons", "ExperimentWorkspaceActions"]
|
||||
},
|
||||
"codersdk.Feature": {
|
||||
"type": "object",
|
||||
|
||||
@@ -1704,9 +1704,6 @@ const (
|
||||
// https://github.com/coder/coder/milestone/19
|
||||
ExperimentWorkspaceActions Experiment = "workspace_actions"
|
||||
|
||||
// New workspace filter
|
||||
ExperimentWorkspaceFilter Experiment = "workspace_filter"
|
||||
|
||||
// Add new experiments here!
|
||||
// ExperimentExample Experiment = "example"
|
||||
)
|
||||
@@ -1715,9 +1712,7 @@ const (
|
||||
// users to opt-in to via --experimental='*'.
|
||||
// Experiments that are not ready for consumption by all users should
|
||||
// not be included here and will be essentially hidden.
|
||||
var ExperimentsAll = Experiments{
|
||||
ExperimentWorkspaceFilter,
|
||||
}
|
||||
var ExperimentsAll = Experiments{}
|
||||
|
||||
// Experiments is a list of experiments that are enabled for the deployment.
|
||||
// Multiple experiments may be enabled at the same time.
|
||||
|
||||
@@ -2490,7 +2490,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
| ------------------- |
|
||||
| `moons` |
|
||||
| `workspace_actions` |
|
||||
| `workspace_filter` |
|
||||
|
||||
## codersdk.Feature
|
||||
|
||||
|
||||
@@ -1344,12 +1344,8 @@ export const Entitlements: Entitlement[] = [
|
||||
]
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export type Experiment = "moons" | "workspace_actions" | "workspace_filter"
|
||||
export const Experiments: Experiment[] = [
|
||||
"moons",
|
||||
"workspace_actions",
|
||||
"workspace_filter",
|
||||
]
|
||||
export type Experiment = "moons" | "workspace_actions"
|
||||
export const Experiments: Experiment[] = ["moons", "workspace_actions"]
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export type FeatureName =
|
||||
|
||||
@@ -74,6 +74,8 @@ export const useFilter = ({
|
||||
}
|
||||
}
|
||||
|
||||
export type UseFilterResult = ReturnType<typeof useFilter>
|
||||
|
||||
const parseFilterQuery = (filterQuery: string): FilterValues => {
|
||||
if (filterQuery === "") {
|
||||
return {}
|
||||
|
||||
@@ -101,3 +101,5 @@ export const useFilterMenu = <TOption extends BaseOption = BaseOption>({
|
||||
isSearching: searchOptionsQuery.isFetching,
|
||||
}
|
||||
}
|
||||
|
||||
export type UseFilterMenuResult = ReturnType<typeof useFilterMenu>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { action } from "@storybook/addon-actions"
|
||||
import { UseFilterResult } from "./filter"
|
||||
import { UseFilterMenuResult } from "./menu"
|
||||
|
||||
export const MockMenu: UseFilterMenuResult = {
|
||||
initialOption: undefined,
|
||||
isInitializing: false,
|
||||
isSearching: false,
|
||||
query: "",
|
||||
searchOptions: [],
|
||||
selectedOption: undefined,
|
||||
selectOption: action("selectOption"),
|
||||
setQuery: action("updateQuery"),
|
||||
}
|
||||
|
||||
export const getDefaultFilterProps = <TFilterProps>({
|
||||
query = "",
|
||||
values,
|
||||
menus,
|
||||
}: {
|
||||
query?: string
|
||||
values: Record<string, string | undefined>
|
||||
menus: Record<string, UseFilterMenuResult>
|
||||
}) =>
|
||||
({
|
||||
filter: {
|
||||
query,
|
||||
update: () => action("update"),
|
||||
debounceUpdate: action("debounce") as UseFilterResult["debounceUpdate"],
|
||||
used: false,
|
||||
values,
|
||||
},
|
||||
menus,
|
||||
} as TFilterProps)
|
||||
@@ -1,47 +0,0 @@
|
||||
import { Story } from "@storybook/react"
|
||||
import { userFilterQuery, workspaceFilterQuery } from "../../utils/filters"
|
||||
import {
|
||||
SearchBarWithFilter,
|
||||
SearchBarWithFilterProps,
|
||||
} from "./SearchBarWithFilter"
|
||||
import { mockApiError } from "testHelpers/entities"
|
||||
|
||||
export default {
|
||||
title: "components/SearchBarWithFilter",
|
||||
component: SearchBarWithFilter,
|
||||
args: {
|
||||
filter: workspaceFilterQuery.me,
|
||||
},
|
||||
}
|
||||
|
||||
const Template: Story<SearchBarWithFilterProps> = (args) => (
|
||||
<SearchBarWithFilter {...args} />
|
||||
)
|
||||
|
||||
export const WithoutPresetFilters = Template.bind({})
|
||||
|
||||
export const WithPresetFilters = Template.bind({})
|
||||
WithPresetFilters.args = {
|
||||
presetFilters: [
|
||||
{ query: workspaceFilterQuery.me, name: "Your workspaces" },
|
||||
{ query: "random query", name: "Random query" },
|
||||
],
|
||||
}
|
||||
|
||||
export const WithError = Template.bind({})
|
||||
WithError.args = {
|
||||
filter: "status:inactive",
|
||||
presetFilters: [
|
||||
{ query: userFilterQuery.active, name: "Active users" },
|
||||
{ query: "random query", name: "Random query" },
|
||||
],
|
||||
error: mockApiError({
|
||||
message: "Invalid user search query.",
|
||||
validations: [
|
||||
{
|
||||
field: "status",
|
||||
detail: `Query param "status" has invalid value: "inactive" is not a valid user status`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { screen } from "@testing-library/react"
|
||||
import userEvent from "@testing-library/user-event"
|
||||
import { render } from "../../testHelpers/renderHelpers"
|
||||
import { SearchBarWithFilter } from "./SearchBarWithFilter"
|
||||
|
||||
// mock the debounce utility
|
||||
jest.mock("just-debounce-it", () =>
|
||||
jest.fn((fn) => {
|
||||
fn.cancel = jest.fn()
|
||||
return fn
|
||||
}),
|
||||
)
|
||||
|
||||
describe("SearchBarWithFilter", () => {
|
||||
it("calls the onFilter handler on keystroke", async () => {
|
||||
// When
|
||||
const onFilter = jest.fn()
|
||||
render(<SearchBarWithFilter onFilter={onFilter} />)
|
||||
|
||||
const searchInput = screen.getByRole("textbox")
|
||||
await userEvent.type(searchInput, "workspace") // 9 characters
|
||||
|
||||
// Then
|
||||
expect(onFilter).toBeCalledTimes(9) // 9 characters
|
||||
})
|
||||
})
|
||||
@@ -1,212 +0,0 @@
|
||||
import Button from "@mui/material/Button"
|
||||
import Fade from "@mui/material/Fade"
|
||||
import InputAdornment from "@mui/material/InputAdornment"
|
||||
import Menu from "@mui/material/Menu"
|
||||
import MenuItem from "@mui/material/MenuItem"
|
||||
import OutlinedInput from "@mui/material/OutlinedInput"
|
||||
import { makeStyles } from "@mui/styles"
|
||||
import { Theme } from "@mui/material/styles"
|
||||
import SearchIcon from "@mui/icons-material/Search"
|
||||
import debounce from "just-debounce-it"
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { getValidationErrorMessage } from "../../api/errors"
|
||||
import { CloseDropdown, OpenDropdown } from "../DropdownArrows/DropdownArrows"
|
||||
import { Stack } from "../Stack/Stack"
|
||||
|
||||
export const Language = {
|
||||
filterName: "Filters",
|
||||
}
|
||||
|
||||
export interface SearchBarWithFilterProps {
|
||||
filter?: string
|
||||
onFilter: (query: string) => void
|
||||
presetFilters?: PresetFilter[]
|
||||
error?: unknown
|
||||
docs?: string
|
||||
}
|
||||
|
||||
export interface PresetFilter {
|
||||
name: string
|
||||
query: string
|
||||
}
|
||||
|
||||
export const SearchBarWithFilter: React.FC<
|
||||
React.PropsWithChildren<SearchBarWithFilterProps>
|
||||
> = ({ filter, onFilter, presetFilters, error, docs }) => {
|
||||
const styles = useStyles({ error: Boolean(error) })
|
||||
const searchInputRef = useRef<HTMLInputElement>(null)
|
||||
const [value, setValue] = useState(filter)
|
||||
useEffect(() => {
|
||||
setValue(filter)
|
||||
}, [filter])
|
||||
// debounce query string entry by user
|
||||
// we want the dependency array empty here
|
||||
// as we don't need to redefine the function
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- see above
|
||||
const debouncedOnFilter = useCallback(
|
||||
debounce((debouncedQueryString: string) => {
|
||||
onFilter(debouncedQueryString)
|
||||
}, 300),
|
||||
[],
|
||||
)
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
const setPresetFilter = (query: string) => () => {
|
||||
if (!searchInputRef.current) {
|
||||
throw new Error("Search input not found.")
|
||||
}
|
||||
|
||||
onFilter(query)
|
||||
// Update this to the input directly instead of create a new state and
|
||||
// re-render the component since the onFilter is already calling the
|
||||
// filtering process
|
||||
searchInputRef.current.value = query
|
||||
handleClose()
|
||||
}
|
||||
|
||||
const errorMessage = getValidationErrorMessage(error)
|
||||
|
||||
return (
|
||||
<Stack spacing={1} className={styles.root}>
|
||||
<Stack direction="row" spacing={0}>
|
||||
{presetFilters && presetFilters.length > 0 && (
|
||||
<Button
|
||||
aria-controls="filter-menu"
|
||||
aria-haspopup="true"
|
||||
size="large"
|
||||
onClick={handleClick}
|
||||
className={styles.buttonRoot}
|
||||
>
|
||||
{Language.filterName}{" "}
|
||||
{anchorEl ? <CloseDropdown /> : <OpenDropdown />}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div role="form" className={styles.filterForm}>
|
||||
<OutlinedInput
|
||||
id="query"
|
||||
name="query"
|
||||
error={Boolean(error)}
|
||||
value={value}
|
||||
className={styles.inputStyles}
|
||||
onChange={(event) => {
|
||||
setValue(event.currentTarget.value)
|
||||
debouncedOnFilter(event.currentTarget.value)
|
||||
}}
|
||||
inputRef={searchInputRef}
|
||||
inputProps={{
|
||||
"aria-label": "Filter",
|
||||
}}
|
||||
startAdornment={
|
||||
<InputAdornment position="start" className={styles.searchIcon}>
|
||||
<SearchIcon fontSize="small" />
|
||||
</InputAdornment>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{presetFilters && presetFilters.length > 0 ? (
|
||||
<Menu
|
||||
id="filter-menu"
|
||||
anchorEl={anchorEl}
|
||||
keepMounted
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleClose}
|
||||
TransitionComponent={Fade}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "left",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
>
|
||||
{presetFilters.map((presetFilter) => (
|
||||
<MenuItem
|
||||
key={presetFilter.name}
|
||||
onClick={setPresetFilter(presetFilter.query)}
|
||||
>
|
||||
{presetFilter.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
{docs && (
|
||||
<MenuItem component="a" href={docs} target="_blank">
|
||||
View advanced filtering
|
||||
</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
) : null}
|
||||
</Stack>
|
||||
{errorMessage && (
|
||||
<Stack className={styles.errorRoot}>{errorMessage}</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
interface StyleProps {
|
||||
error?: boolean
|
||||
}
|
||||
|
||||
const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
|
||||
root: {
|
||||
marginBottom: theme.spacing(2),
|
||||
|
||||
"&:has(button) .MuiInputBase-root": {
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
},
|
||||
},
|
||||
// necessary to expand the textField
|
||||
// the length of the page (within the bordered filterContainer)
|
||||
filterForm: {
|
||||
width: "100%",
|
||||
},
|
||||
buttonRoot: {
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`,
|
||||
flexShrink: 0,
|
||||
marginRight: -1,
|
||||
|
||||
"&:hover": {
|
||||
zIndex: 1,
|
||||
},
|
||||
},
|
||||
errorRoot: {
|
||||
color: theme.palette.error.main,
|
||||
whiteSpace: "pre-wrap",
|
||||
},
|
||||
inputStyles: {
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
color: theme.palette.primary.contrastText,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
|
||||
"& fieldset": {
|
||||
borderColor: theme.palette.divider,
|
||||
"&MuiOutlinedInput-root:hover, &MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: (props) => props.error && theme.palette.error.contrastText,
|
||||
},
|
||||
},
|
||||
|
||||
"& .MuiInputBase-input": {
|
||||
paddingTop: "inherit",
|
||||
paddingBottom: "inherit",
|
||||
// The same as the button
|
||||
minHeight: 40,
|
||||
},
|
||||
},
|
||||
searchIcon: {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
}))
|
||||
@@ -7,14 +7,12 @@ import { pageTitle } from "utils/page"
|
||||
import { AuditPageView } from "./AuditPageView"
|
||||
import { useUserFilterMenu } from "components/Filter/UserFilter"
|
||||
import { useFilter } from "components/Filter/filter"
|
||||
import { useDashboard } from "components/Dashboard/DashboardProvider"
|
||||
import { usePagination } from "hooks"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { getAuditLogs } from "api/api"
|
||||
import { useActionFilterMenu, useResourceTypeFilterMenu } from "./AuditFilter"
|
||||
|
||||
const AuditPage: FC = () => {
|
||||
const dashboard = useDashboard()
|
||||
const searchParamsResult = useSearchParams()
|
||||
const pagination = usePagination({ searchParamsResult })
|
||||
const filter = useFilter({
|
||||
@@ -73,22 +71,15 @@ const AuditPage: FC = () => {
|
||||
isNonInitialPage={nonInitialPage(searchParamsResult[0])}
|
||||
isAuditLogVisible={isAuditLogVisible}
|
||||
error={error}
|
||||
filterProps={
|
||||
dashboard.experiments.includes("workspace_filter")
|
||||
? {
|
||||
filter,
|
||||
error,
|
||||
menus: {
|
||||
user: userMenu,
|
||||
action: actionMenu,
|
||||
resourceType: resourceTypeMenu,
|
||||
},
|
||||
}
|
||||
: {
|
||||
filter: filter.query,
|
||||
onFilter: filter.update,
|
||||
}
|
||||
}
|
||||
filterProps={{
|
||||
filter,
|
||||
error,
|
||||
menus: {
|
||||
user: userMenu,
|
||||
action: actionMenu,
|
||||
resourceType: resourceTypeMenu,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,41 +1,25 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair -- ignore */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any -- We don't care about any here */
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import { MockAuditLog, MockAuditLog2, MockUser } from "testHelpers/entities"
|
||||
import { AuditPageView } from "./AuditPageView"
|
||||
import { action } from "@storybook/addon-actions"
|
||||
import { WorkspacesPageView } from "pages/WorkspacesPage/WorkspacesPageView"
|
||||
import { ComponentProps } from "react"
|
||||
import { MockMenu, getDefaultFilterProps } from "components/Filter/storyHelpers"
|
||||
|
||||
const mockMenu = {
|
||||
initialOption: undefined,
|
||||
isInitializing: false,
|
||||
isSearching: false,
|
||||
query: "",
|
||||
searchOptions: [],
|
||||
selectedOption: undefined,
|
||||
selectOption: action("selectOption"),
|
||||
setQuery: action("updateQuery"),
|
||||
}
|
||||
type FilterProps = ComponentProps<typeof AuditPageView>["filterProps"]
|
||||
|
||||
const defaultFilterProps = {
|
||||
filter: {
|
||||
query: `owner:me`,
|
||||
update: () => action("update"),
|
||||
debounceUpdate: action("debounce") as any,
|
||||
used: false,
|
||||
values: {
|
||||
username: MockUser.username,
|
||||
action: undefined,
|
||||
resource_type: undefined,
|
||||
},
|
||||
const defaultFilterProps = getDefaultFilterProps<FilterProps>({
|
||||
query: `owner:me`,
|
||||
values: {
|
||||
username: MockUser.username,
|
||||
action: undefined,
|
||||
resource_type: undefined,
|
||||
},
|
||||
menus: {
|
||||
user: mockMenu,
|
||||
action: mockMenu,
|
||||
resourceType: mockMenu,
|
||||
user: MockMenu,
|
||||
action: MockMenu,
|
||||
resourceType: MockMenu,
|
||||
},
|
||||
} as ComponentProps<typeof AuditPageView>["filterProps"]
|
||||
})
|
||||
|
||||
const meta: Meta<typeof AuditPageView> = {
|
||||
title: "pages/AuditPageView",
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
PageHeaderSubtitle,
|
||||
PageHeaderTitle,
|
||||
} from "components/PageHeader/PageHeader"
|
||||
import { SearchBarWithFilter } from "components/SearchBarWithFilter/SearchBarWithFilter"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { TableLoader } from "components/TableLoader/TableLoader"
|
||||
import { Timeline } from "components/Timeline/Timeline"
|
||||
@@ -30,23 +29,6 @@ export const Language = {
|
||||
subtitle: "View events in your audit log.",
|
||||
}
|
||||
|
||||
const presetFilters = [
|
||||
{
|
||||
query: "resource_type:workspace action:create",
|
||||
name: "Created workspaces",
|
||||
},
|
||||
{ query: "resource_type:template action:create", name: "Added templates" },
|
||||
{ query: "resource_type:user action:delete", name: "Deleted users" },
|
||||
{
|
||||
query: "resource_type:workspace_build action:start build_reason:initiator",
|
||||
name: "Builds started by a user",
|
||||
},
|
||||
{
|
||||
query: "resource_type:api_key action:login",
|
||||
name: "User logins",
|
||||
},
|
||||
]
|
||||
|
||||
export interface AuditPageViewProps {
|
||||
auditLogs?: AuditLog[]
|
||||
count?: number
|
||||
@@ -56,9 +38,7 @@ export interface AuditPageViewProps {
|
||||
isNonInitialPage: boolean
|
||||
isAuditLogVisible: boolean
|
||||
error?: Error | unknown
|
||||
filterProps:
|
||||
| ComponentProps<typeof SearchBarWithFilter>
|
||||
| ComponentProps<typeof AuditFilter>
|
||||
filterProps: ComponentProps<typeof AuditFilter>
|
||||
}
|
||||
|
||||
export const AuditPageView: FC<AuditPageViewProps> = ({
|
||||
@@ -91,16 +71,7 @@ export const AuditPageView: FC<AuditPageViewProps> = ({
|
||||
|
||||
<ChooseOne>
|
||||
<Cond condition={isAuditLogVisible}>
|
||||
{"onFilter" in filterProps ? (
|
||||
<SearchBarWithFilter
|
||||
{...filterProps}
|
||||
docs="https://coder.com/docs/coder-oss/latest/admin/audit-logs#filtering-logs"
|
||||
presetFilters={presetFilters}
|
||||
error={error}
|
||||
/>
|
||||
) : (
|
||||
<AuditFilter {...filterProps} />
|
||||
)}
|
||||
<AuditFilter {...filterProps} />
|
||||
|
||||
<PaginationStatus
|
||||
isLoading={Boolean(isLoading)}
|
||||
|
||||
@@ -18,7 +18,6 @@ import { ResetPasswordDialog } from "../../components/Dialogs/ResetPasswordDialo
|
||||
import { pageTitle } from "../../utils/page"
|
||||
import { UsersPageView } from "./UsersPageView"
|
||||
import { useStatusFilterMenu } from "./UsersFilter"
|
||||
import { useDashboard } from "components/Dashboard/DashboardProvider"
|
||||
import { useFilter } from "components/Filter/filter"
|
||||
|
||||
export const Language = {
|
||||
@@ -77,8 +76,6 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
|
||||
|
||||
const me = useMe()
|
||||
|
||||
// New filter
|
||||
const dashboard = useDashboard()
|
||||
const useFilterResult = useFilter({
|
||||
searchParamsResult,
|
||||
onUpdate: () => {
|
||||
@@ -148,29 +145,19 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
|
||||
roles,
|
||||
})
|
||||
}}
|
||||
error={getUsersError}
|
||||
isUpdatingUserRoles={usersState.matches("updatingUserRoles")}
|
||||
isLoading={isLoading}
|
||||
canEditUsers={canEditUsers}
|
||||
paginationRef={paginationRef}
|
||||
isNonInitialPage={nonInitialPage(searchParams)}
|
||||
actorID={me.id}
|
||||
filterProps={
|
||||
dashboard.experiments.includes("workspace_filter")
|
||||
? {
|
||||
filter: useFilterResult,
|
||||
error: getUsersError,
|
||||
menus: {
|
||||
status: statusMenu,
|
||||
},
|
||||
}
|
||||
: {
|
||||
filter: usersState.context.filter,
|
||||
onFilter: (query) => {
|
||||
usersSend({ type: "UPDATE_FILTER", query })
|
||||
},
|
||||
}
|
||||
}
|
||||
filterProps={{
|
||||
filter: useFilterResult,
|
||||
error: getUsersError,
|
||||
menus: {
|
||||
status: statusMenu,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<DeleteDialog
|
||||
|
||||
@@ -7,7 +7,20 @@ import {
|
||||
mockApiError,
|
||||
} from "testHelpers/entities"
|
||||
import { UsersPageView } from "./UsersPageView"
|
||||
import { action } from "@storybook/addon-actions"
|
||||
import { ComponentProps } from "react"
|
||||
import { MockMenu, getDefaultFilterProps } from "components/Filter/storyHelpers"
|
||||
|
||||
type FilterProps = ComponentProps<typeof UsersPageView>["filterProps"]
|
||||
|
||||
const defaultFilterProps = getDefaultFilterProps<FilterProps>({
|
||||
query: "owner:me",
|
||||
menus: {
|
||||
status: MockMenu,
|
||||
},
|
||||
values: {
|
||||
status: "active",
|
||||
},
|
||||
})
|
||||
|
||||
const meta: Meta<typeof UsersPageView> = {
|
||||
title: "pages/UsersPageView",
|
||||
@@ -19,10 +32,7 @@ const meta: Meta<typeof UsersPageView> = {
|
||||
roles: MockAssignableSiteRoles,
|
||||
count: 2,
|
||||
canEditUsers: true,
|
||||
filterProps: {
|
||||
onFilter: action("onFilter"),
|
||||
filter: "",
|
||||
},
|
||||
filterProps: defaultFilterProps,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -56,14 +66,18 @@ export const EmptyPage = {
|
||||
export const Error = {
|
||||
args: {
|
||||
users: undefined,
|
||||
error: mockApiError({
|
||||
message: "Invalid user search query.",
|
||||
validations: [
|
||||
{
|
||||
field: "status",
|
||||
detail: `Query param "status" has invalid value: "inactive" is not a valid user status`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
count: 0,
|
||||
filterProps: {
|
||||
...defaultFilterProps,
|
||||
error: mockApiError({
|
||||
message: "Invalid user search query.",
|
||||
validations: [
|
||||
{
|
||||
field: "status",
|
||||
detail: `Query param "status" has invalid value: "inactive" is not a valid user status`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ import { PaginationWidget } from "components/PaginationWidget/PaginationWidget"
|
||||
import { ComponentProps, FC } from "react"
|
||||
import { PaginationMachineRef } from "xServices/pagination/paginationXService"
|
||||
import * as TypesGen from "../../api/typesGenerated"
|
||||
import { SearchBarWithFilter } from "../../components/SearchBarWithFilter/SearchBarWithFilter"
|
||||
import { UsersTable } from "../../components/UsersTable/UsersTable"
|
||||
import { userFilterQuery } from "../../utils/filters"
|
||||
import { UsersFilter } from "./UsersFilter"
|
||||
import { PaginationStatus } from "components/PaginationStatus/PaginationStatus"
|
||||
|
||||
@@ -16,7 +14,6 @@ export interface UsersPageViewProps {
|
||||
users?: TypesGen.User[]
|
||||
count?: number
|
||||
roles?: TypesGen.AssignableRoles[]
|
||||
error?: unknown
|
||||
isUpdatingUserRoles?: boolean
|
||||
canEditUsers?: boolean
|
||||
isLoading?: boolean
|
||||
@@ -30,9 +27,7 @@ export interface UsersPageViewProps {
|
||||
user: TypesGen.User,
|
||||
roles: TypesGen.Role["name"][],
|
||||
) => void
|
||||
filterProps:
|
||||
| ComponentProps<typeof SearchBarWithFilter>
|
||||
| ComponentProps<typeof UsersFilter>
|
||||
filterProps: ComponentProps<typeof UsersFilter>
|
||||
paginationRef: PaginationMachineRef
|
||||
isNonInitialPage: boolean
|
||||
actorID: string
|
||||
@@ -49,7 +44,6 @@ export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
|
||||
onActivateUser,
|
||||
onResetUserPassword,
|
||||
onUpdateUserRoles,
|
||||
error,
|
||||
isUpdatingUserRoles,
|
||||
canEditUsers,
|
||||
isLoading,
|
||||
@@ -58,22 +52,9 @@ export const UsersPageView: FC<React.PropsWithChildren<UsersPageViewProps>> = ({
|
||||
isNonInitialPage,
|
||||
actorID,
|
||||
}) => {
|
||||
const presetFilters = [
|
||||
{ query: userFilterQuery.active, name: Language.activeUsersFilterName },
|
||||
{ query: userFilterQuery.all, name: Language.allUsersFilterName },
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
{"onFilter" in filterProps ? (
|
||||
<SearchBarWithFilter
|
||||
{...filterProps}
|
||||
presetFilters={presetFilters}
|
||||
error={error}
|
||||
/>
|
||||
) : (
|
||||
<UsersFilter {...filterProps} />
|
||||
)}
|
||||
<UsersFilter {...filterProps} />
|
||||
|
||||
<PaginationStatus
|
||||
isLoading={Boolean(isLoading)}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { WorkspacesPageView } from "./WorkspacesPageView"
|
||||
import { useOrganizationId, usePermissions } from "hooks"
|
||||
import { useTemplateFilterMenu, useStatusFilterMenu } from "./filter/menus"
|
||||
import { useSearchParams } from "react-router-dom"
|
||||
import { useDashboard } from "components/Dashboard/DashboardProvider"
|
||||
import { useFilter } from "components/Filter/filter"
|
||||
import { useUserFilterMenu } from "components/Filter/UserFilter"
|
||||
|
||||
@@ -49,7 +48,6 @@ const WorkspacesPage: FC = () => {
|
||||
onChange: (option) =>
|
||||
filter.update({ ...filter.values, status: option?.value }),
|
||||
})
|
||||
const dashboard = useDashboard()
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -58,7 +56,6 @@ const WorkspacesPage: FC = () => {
|
||||
</Helmet>
|
||||
|
||||
<WorkspacesPageView
|
||||
useNewFilter={dashboard.experiments.includes("workspace_filter")}
|
||||
workspaces={data?.workspaces}
|
||||
error={error}
|
||||
count={data?.count}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable eslint-comments/disable-enable-pair -- ignore */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any -- We don't care about any here */
|
||||
import { Meta, StoryObj } from "@storybook/react"
|
||||
import { DEFAULT_RECORDS_PER_PAGE } from "components/PaginationWidget/utils"
|
||||
import dayjs from "dayjs"
|
||||
@@ -15,13 +13,13 @@ import {
|
||||
MockBuildInfo,
|
||||
MockEntitlementsWithScheduling,
|
||||
MockExperiments,
|
||||
MockUser,
|
||||
mockApiError,
|
||||
MockUser,
|
||||
} from "testHelpers/entities"
|
||||
import { WorkspacesPageView } from "./WorkspacesPageView"
|
||||
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider"
|
||||
import { action } from "@storybook/addon-actions"
|
||||
import { ComponentProps } from "react"
|
||||
import { MockMenu, getDefaultFilterProps } from "components/Filter/storyHelpers"
|
||||
|
||||
const createWorkspace = (
|
||||
status: WorkspaceStatus,
|
||||
@@ -71,35 +69,21 @@ const MockedAppearance = {
|
||||
save: () => null,
|
||||
}
|
||||
|
||||
const mockMenu = {
|
||||
initialOption: undefined,
|
||||
isInitializing: false,
|
||||
isSearching: false,
|
||||
query: "",
|
||||
searchOptions: [],
|
||||
selectedOption: undefined,
|
||||
selectOption: action("selectOption"),
|
||||
setQuery: action("updateQuery"),
|
||||
}
|
||||
type FilterProps = ComponentProps<typeof WorkspacesPageView>["filterProps"]
|
||||
|
||||
const defaultFilterProps = {
|
||||
filter: {
|
||||
query: `owner:me`,
|
||||
update: () => action("update"),
|
||||
debounceUpdate: action("debounce") as any,
|
||||
used: false,
|
||||
values: {
|
||||
owner: MockUser.username,
|
||||
template: undefined,
|
||||
status: undefined,
|
||||
},
|
||||
},
|
||||
const defaultFilterProps = getDefaultFilterProps<FilterProps>({
|
||||
query: "owner:me",
|
||||
menus: {
|
||||
user: mockMenu,
|
||||
template: mockMenu,
|
||||
status: mockMenu,
|
||||
user: MockMenu,
|
||||
template: MockMenu,
|
||||
status: MockMenu,
|
||||
},
|
||||
} as ComponentProps<typeof WorkspacesPageView>["filterProps"]
|
||||
values: {
|
||||
owner: MockUser.username,
|
||||
template: undefined,
|
||||
status: undefined,
|
||||
},
|
||||
})
|
||||
|
||||
const meta: Meta<typeof WorkspacesPageView> = {
|
||||
title: "pages/WorkspacesPageView",
|
||||
|
||||
@@ -19,8 +19,6 @@ import { ImpendingDeletionBanner, Count } from "components/WorkspaceDeletion"
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert"
|
||||
import { WorkspacesFilter } from "./filter/filter"
|
||||
import { hasError, isApiValidationError } from "api/errors"
|
||||
import { workspaceFilterQuery } from "utils/filters"
|
||||
import { SearchBarWithFilter } from "components/SearchBarWithFilter/SearchBarWithFilter"
|
||||
import { PaginationStatus } from "components/PaginationStatus/PaginationStatus"
|
||||
|
||||
export const Language = {
|
||||
@@ -32,24 +30,10 @@ export const Language = {
|
||||
template: "Template",
|
||||
}
|
||||
|
||||
const presetFilters = [
|
||||
{ query: workspaceFilterQuery.me, name: Language.yourWorkspacesButton },
|
||||
{ query: workspaceFilterQuery.all, name: Language.allWorkspacesButton },
|
||||
{
|
||||
query: workspaceFilterQuery.running,
|
||||
name: Language.runningWorkspacesButton,
|
||||
},
|
||||
{
|
||||
query: workspaceFilterQuery.failed,
|
||||
name: "Failed workspaces",
|
||||
},
|
||||
]
|
||||
|
||||
export interface WorkspacesPageViewProps {
|
||||
error: unknown
|
||||
workspaces?: Workspace[]
|
||||
count?: number
|
||||
useNewFilter?: boolean
|
||||
filterProps: ComponentProps<typeof WorkspacesFilter>
|
||||
page: number
|
||||
limit: number
|
||||
@@ -67,7 +51,6 @@ export const WorkspacesPageView: FC<
|
||||
filterProps,
|
||||
onPageChange,
|
||||
onUpdateWorkspace,
|
||||
useNewFilter,
|
||||
page,
|
||||
}) => {
|
||||
const { saveLocal, getLocal } = useLocalStorage()
|
||||
@@ -133,16 +116,7 @@ export const WorkspacesPageView: FC<
|
||||
count={Count.Multiple}
|
||||
/>
|
||||
|
||||
{useNewFilter ? (
|
||||
<WorkspacesFilter error={error} {...filterProps} />
|
||||
) : (
|
||||
<SearchBarWithFilter
|
||||
filter={filterProps.filter.query}
|
||||
onFilter={filterProps.filter.debounceUpdate}
|
||||
presetFilters={presetFilters}
|
||||
error={error}
|
||||
/>
|
||||
)}
|
||||
<WorkspacesFilter error={error} {...filterProps} />
|
||||
</Stack>
|
||||
|
||||
<PaginationStatus
|
||||
|
||||
@@ -1405,7 +1405,6 @@ export const MockEntitlementsWithScheduling: TypesGen.Entitlements = {
|
||||
export const MockExperiments: TypesGen.Experiment[] = [
|
||||
"workspace_actions",
|
||||
"moons",
|
||||
"workspace_filter",
|
||||
]
|
||||
|
||||
export const MockAuditLog: TypesGen.AuditLog = {
|
||||
|
||||
Reference in New Issue
Block a user