From d7c1318eacbeb8c3a0466ceb9ec166b79fa44ecc Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 1 Jun 2022 23:13:27 +0000 Subject: [PATCH 01/22] feat: Workspaces filtering --- .../pages/WorkspacesPage/WorkspacesPage.tsx | 91 ++++++++++- .../WorkspacesPage/WorkspacesPageView.tsx | 141 ++++++++---------- site/src/util/workspace.test.ts | 13 +- site/src/util/workspace.ts | 22 +++ .../workspaces/workspacesXService.ts | 29 +++- 5 files changed, 203 insertions(+), 93 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index 3d20b318c4e26..5e143771e9805 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -1,20 +1,95 @@ +import Button from "@material-ui/core/Button" +import Link from "@material-ui/core/Link" +import Menu from "@material-ui/core/Menu" +import MenuItem from "@material-ui/core/MenuItem" +import { makeStyles } from "@material-ui/core/styles" +import TextField from "@material-ui/core/TextField" +import AddCircleOutline from "@material-ui/icons/AddCircleOutline" import { useMachine } from "@xstate/react" -import { FC } from "react" +import { FormikContextType, FormikErrors, useFormik } from "formik" +import { FC, useState } from "react" +import { Link as RouterLink } from "react-router-dom" +import { Margins } from "../../components/Margins/Margins" +import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils" import { workspacesMachine } from "../../xServices/workspaces/workspacesXService" -import { WorkspacesPageView } from "./WorkspacesPageView" +import { Language, WorkspacesPageView } from "./WorkspacesPageView" + +interface FilterFormValues { + query: string +} + +export type FilterFormErrors = FormikErrors const WorkspacesPage: FC = () => { - const [workspacesState] = useMachine(workspacesMachine) + const styles = useStyles() + const [workspacesState, send] = useMachine(workspacesMachine) + + const form: FormikContextType = useFormik({ + initialValues: { query: workspacesState.context.filter || "" }, + onSubmit: (data) => { + send({ + type: "SET_FILTER", + query: data.query, + }) + }, + }) + + const getFieldHelpers = getFormHelpers(form, {}) + + const [anchorEl, setAnchorEl] = useState(null) + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget) + } + const handleClose = () => { + setAnchorEl(null) + } + const setYourWorkspaces = () => { + form.setFieldValue("query", "owner:me") + void form.submitForm() + handleClose() + } + const setAllWorkspaces = () => { + form.setFieldValue("query", "") + void form.submitForm() + handleClose() + } return ( <> - + +
+ + + Your workspaces + All workspaces + +
+ + + + + +
+ +
) } +const useStyles = makeStyles((theme) => ({ + actions: { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(3), + display: "flex", + justifyContent: "space-between", + height: theme.spacing(6), + }, +})) + export default WorkspacesPage diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index 2a412116be7bb..71a4289753b75 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -15,7 +15,7 @@ import { Link as RouterLink } from "react-router-dom" import * as TypesGen from "../../api/typesGenerated" import { AvatarData } from "../../components/AvatarData/AvatarData" import { EmptyState } from "../../components/EmptyState/EmptyState" -import { Margins } from "../../components/Margins/Margins" +import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" import { Stack } from "../../components/Stack/Stack" import { TableLoader } from "../../components/TableLoader/TableLoader" import { getDisplayStatus } from "../../util/workspace" @@ -34,93 +34,78 @@ export interface WorkspacesPageViewProps { error?: unknown } -export const WorkspacesPageView: FC = (props) => { - const styles = useStyles() +export const WorkspacesPageView: FC = ({ loading, workspaces, error }) => { + useStyles() const theme: Theme = useTheme() + return ( - -
- - - -
- - + {error && } +
+ + + Name + Template + Version + Last Built + Status + + + + {loading && } + {workspaces && workspaces.length === 0 && ( - Name - Template - Version - Last Built - Status + + + + + } + /> + - - - {props.loading && } - {props.workspaces && props.workspaces.length === 0 && ( - - - - - - } - /> - - - )} - {props.workspaces && - props.workspaces.map((workspace) => { - const status = getDisplayStatus(theme, workspace.latest_build) - return ( - - - - - {workspace.template_name} - - {workspace.outdated ? ( - outdated - ) : ( - up to date - )} - - - - {dayjs().to(dayjs(workspace.latest_build.created_at))} - - - - {status.status} - - - ) - })} - -
-
+ )} + {workspaces && + workspaces.map((workspace) => { + const status = getDisplayStatus(theme, workspace.latest_build) + return ( + + + + + {workspace.template_name} + + {workspace.outdated ? ( + outdated + ) : ( + up to date + )} + + + + {dayjs().to(dayjs(workspace.latest_build.created_at))} + + + + {status.status} + + + ) + })} + +
) } const useStyles = makeStyles((theme) => ({ - actions: { - marginTop: theme.spacing(3), - marginBottom: theme.spacing(3), - display: "flex", - height: theme.spacing(6), - - "& > *": { - marginLeft: "auto", - }, - }, welcome: { paddingTop: theme.spacing(12), paddingBottom: theme.spacing(12), diff --git a/site/src/util/workspace.test.ts b/site/src/util/workspace.test.ts index f14f5a04bdc40..acda5623c9be6 100644 --- a/site/src/util/workspace.test.ts +++ b/site/src/util/workspace.test.ts @@ -1,7 +1,7 @@ import dayjs from "dayjs" import * as TypesGen from "../api/typesGenerated" import * as Mocks from "../testHelpers/entities" -import { defaultWorkspaceExtension, isWorkspaceOn } from "./workspace" +import { isWorkspaceOn, workspaceQueryToFilter, defaultWorkspaceExtension } from "./workspace" describe("util > workspace", () => { describe("isWorkspaceOn", () => { @@ -61,6 +61,17 @@ describe("util > workspace", () => { ], ])(`defaultWorkspaceExtension(%p) returns %p`, (startTime, request) => { expect(defaultWorkspaceExtension(dayjs(startTime))).toEqual(request) + }) + }) + describe("workspaceQueryToFilter", () => { + it.each<[string | undefined, TypesGen.WorkspaceFilter]>([ + [undefined, { Owner: "", OrganizationID: "" }], + ["", { Owner: "", OrganizationID: "" }], + ["asdkfvjn", { Owner: "", OrganizationID: "" }], + ["owner:me", { Owner: "me", OrganizationID: "" }], + ["owner:me owner:me2", { Owner: "me2", OrganizationID: "" }], + ])(`query=%p, filter=%p`, (query, filter) => { + expect(workspaceQueryToFilter(query)).toBe(filter) }) }) }) diff --git a/site/src/util/workspace.ts b/site/src/util/workspace.ts index f717e262a4587..7f6cb56dd5a03 100644 --- a/site/src/util/workspace.ts +++ b/site/src/util/workspace.ts @@ -207,3 +207,25 @@ export const defaultWorkspaceExtension = (__startDate?: dayjs.Dayjs): TypesGen.P deadline: NinetyMinutesFromNow.format(), } } + +export const workspaceQueryToFilter = (query?: string): WorkspaceFilter => { + let filter: WorkspaceFilter = { + Owner: "", + OrganizationID: "", + } + + if (query) { + const parts = query.split(" ") + + parts.map((part) => { + if (part.startsWith("owner:")) { + filter = { + Owner: part.slice("owner:".length), + OrganizationID: "", + } + } + }) + } + + return filter +} diff --git a/site/src/xServices/workspaces/workspacesXService.ts b/site/src/xServices/workspaces/workspacesXService.ts index 353e71b234a73..a5e94c9559a04 100644 --- a/site/src/xServices/workspaces/workspacesXService.ts +++ b/site/src/xServices/workspaces/workspacesXService.ts @@ -1,13 +1,15 @@ import { assign, createMachine } from "xstate" import * as API from "../../api/api" import * as TypesGen from "../../api/typesGenerated" +import { workspaceQueryToFilter } from "../../util/workspace" interface WorkspaceContext { workspaces?: TypesGen.Workspace[] + filter?: string getWorkspacesError?: Error | unknown } -type WorkspaceEvent = { type: "GET_WORKSPACE"; workspaceId: string } +type WorkspaceEvent = { type: "GET_WORKSPACE"; workspaceId: string } | { type: "SET_FILTER"; query: string } export const workspacesMachine = createMachine( { @@ -22,26 +24,38 @@ export const workspacesMachine = createMachine( }, }, id: "workspaceState", + context: { + filter: "owner:me", + }, initial: "gettingWorkspaces", states: { + ready: { + on: { + SET_FILTER: "extractingFilter", + }, + }, + extractingFilter: { + entry: "assignFilter", + always: { + target: "gettingWorkspaces", + }, + }, gettingWorkspaces: { entry: "clearGetWorkspacesError", invoke: { src: "getWorkspaces", id: "getWorkspaces", onDone: { - target: "done", + target: "ready", actions: ["assignWorkspaces", "clearGetWorkspacesError"], }, onError: { - target: "error", + target: "ready", actions: "assignGetWorkspacesError", }, }, tags: "loading", }, - done: {}, - error: {}, }, }, { @@ -49,13 +63,16 @@ export const workspacesMachine = createMachine( assignWorkspaces: assign({ workspaces: (_, event) => event.data, }), + assignFilter: assign({ + filter: (_, event) => event.query, + }), assignGetWorkspacesError: assign({ getWorkspacesError: (_, event) => event.data, }), clearGetWorkspacesError: (context) => assign({ ...context, getWorkspacesError: undefined }), }, services: { - getWorkspaces: () => API.getWorkspaces(), + getWorkspaces: (context) => API.getWorkspaces(workspaceQueryToFilter(context.filter)), }, }, ) From 5cb62d511920908d7e269bb9487618bbc0ef5f53 Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 1 Jun 2022 23:16:18 +0000 Subject: [PATCH 02/22] Remove error handling --- site/src/pages/WorkspacesPage/WorkspacesPageView.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index 71a4289753b75..0929b6e241acc 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -15,7 +15,6 @@ import { Link as RouterLink } from "react-router-dom" import * as TypesGen from "../../api/typesGenerated" import { AvatarData } from "../../components/AvatarData/AvatarData" import { EmptyState } from "../../components/EmptyState/EmptyState" -import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" import { Stack } from "../../components/Stack/Stack" import { TableLoader } from "../../components/TableLoader/TableLoader" import { getDisplayStatus } from "../../util/workspace" @@ -34,13 +33,12 @@ export interface WorkspacesPageViewProps { error?: unknown } -export const WorkspacesPageView: FC = ({ loading, workspaces, error }) => { +export const WorkspacesPageView: FC = ({ loading, workspaces }) => { useStyles() const theme: Theme = useTheme() return ( - {error && } From 09011d99594a3016a2f7aaf3549762d28a28f162 Mon Sep 17 00:00:00 2001 From: Garrett Date: Wed, 1 Jun 2022 23:25:10 +0000 Subject: [PATCH 03/22] styling --- .../pages/WorkspacesPage/WorkspacesPage.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index 5e143771e9805..da431d5fd7f6e 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -10,6 +10,7 @@ import { FormikContextType, FormikErrors, useFormik } from "formik" import { FC, useState } from "react" import { Link as RouterLink } from "react-router-dom" import { Margins } from "../../components/Margins/Margins" +import { Stack } from "../../components/Stack/Stack" import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils" import { workspacesMachine } from "../../xServices/workspaces/workspacesXService" import { Language, WorkspacesPageView } from "./WorkspacesPageView" @@ -58,16 +59,19 @@ const WorkspacesPage: FC = () => { <>
- - - Your workspaces - All workspaces - -
- - + + + + Your workspaces + All workspaces + +
+ + +
+ From 11836ea2886349ab4299cd97bca5e0d58f024a6d Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 2 Jun 2022 11:11:10 -0500 Subject: [PATCH 04/22] Apply suggestions from code review Co-authored-by: G r e y --- .../pages/WorkspacesPage/WorkspacesPage.tsx | 23 +++++++++++------ site/src/util/workspace.test.ts | 4 +-- site/src/util/workspace.ts | 25 +++++++++++-------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index da431d5fd7f6e..2268971adcb40 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -25,30 +25,36 @@ const WorkspacesPage: FC = () => { const styles = useStyles() const [workspacesState, send] = useMachine(workspacesMachine) - const form: FormikContextType = useFormik({ - initialValues: { query: workspacesState.context.filter || "" }, - onSubmit: (data) => { +const form = useFormik({ + initialValues: { + query: workspacesState.context.filter || "", + }, + onSubmit: (values) => { send({ type: "SET_FILTER", - query: data.query, + query: values.query, }) }, }) - const getFieldHelpers = getFormHelpers(form, {}) + const getFieldHelpers = getFormHelpers(form) const [anchorEl, setAnchorEl] = useState(null) + const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget) } + const handleClose = () => { setAnchorEl(null) } + const setYourWorkspaces = () => { form.setFieldValue("query", "owner:me") void form.submitForm() handleClose() } + const setAllWorkspaces = () => { form.setFieldValue("query", "") void form.submitForm() @@ -60,13 +66,15 @@ const WorkspacesPage: FC = () => {
- - + + Your workspaces All workspaces +
@@ -76,6 +84,7 @@ const WorkspacesPage: FC = () => {
+ workspace", () => { ["", { Owner: "", OrganizationID: "" }], ["asdkfvjn", { Owner: "", OrganizationID: "" }], ["owner:me", { Owner: "me", OrganizationID: "" }], - ["owner:me owner:me2", { Owner: "me2", OrganizationID: "" }], + ["owner:me owner:me2", { Owner: "me", OrganizationID: "" }], ])(`query=%p, filter=%p`, (query, filter) => { - expect(workspaceQueryToFilter(query)).toBe(filter) + expect(workspaceQueryToFilter(query)).toEqual(filter) }) }) }) diff --git a/site/src/util/workspace.ts b/site/src/util/workspace.ts index 7f6cb56dd5a03..d174072253b76 100644 --- a/site/src/util/workspace.ts +++ b/site/src/util/workspace.ts @@ -209,23 +209,28 @@ export const defaultWorkspaceExtension = (__startDate?: dayjs.Dayjs): TypesGen.P } export const workspaceQueryToFilter = (query?: string): WorkspaceFilter => { - let filter: WorkspaceFilter = { + const defaultFilter: WorkspaceFilter = { Owner: "", OrganizationID: "", } - if (query) { - const parts = query.split(" ") + const preparedQuery = query?.replace(/ +/g, " ") - parts.map((part) => { - if (part.startsWith("owner:")) { - filter = { - Owner: part.slice("owner:".length), + if (!preparedQuery) { + return defaultFilter + } else { + const parts = preparedQuery.split(" ") + + for (const part of parts) { + const [key, val] = part.split(":") + if (key === "owner") { + return { + Owner: val, OrganizationID: "", } } - }) - } + } - return filter + return defaultFilter + } } From 568e3a61ec55d0e0d1cd8963074f36aec0573731 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 16:18:48 +0000 Subject: [PATCH 05/22] pr fixes --- site/src/pages/WorkspacesPage/WorkspacesPage.tsx | 9 ++++----- site/src/pages/WorkspacesPage/WorkspacesPageView.tsx | 1 - site/src/util/workspace.ts | 1 + site/src/xServices/workspaces/workspacesXService.ts | 3 ++- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index 2268971adcb40..b64903edac2a1 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -6,7 +6,7 @@ import { makeStyles } from "@material-ui/core/styles" import TextField from "@material-ui/core/TextField" import AddCircleOutline from "@material-ui/icons/AddCircleOutline" import { useMachine } from "@xstate/react" -import { FormikContextType, FormikErrors, useFormik } from "formik" +import { FormikErrors, useFormik } from "formik" import { FC, useState } from "react" import { Link as RouterLink } from "react-router-dom" import { Margins } from "../../components/Margins/Margins" @@ -25,7 +25,7 @@ const WorkspacesPage: FC = () => { const styles = useStyles() const [workspacesState, send] = useMachine(workspacesMachine) -const form = useFormik({ + const form = useFormik({ initialValues: { query: workspacesState.context.filter || "", }, @@ -50,13 +50,13 @@ const form = useFormik({ } const setYourWorkspaces = () => { - form.setFieldValue("query", "owner:me") + void form.setFieldValue("query", "owner:me") void form.submitForm() handleClose() } const setAllWorkspaces = () => { - form.setFieldValue("query", "") + void form.setFieldValue("query", "") void form.submitForm() handleClose() } @@ -88,7 +88,6 @@ const form = useFormik({
diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index 0929b6e241acc..bc49893e2e94b 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -30,7 +30,6 @@ export const Language = { export interface WorkspacesPageViewProps { loading?: boolean workspaces?: TypesGen.Workspace[] - error?: unknown } export const WorkspacesPageView: FC = ({ loading, workspaces }) => { diff --git a/site/src/util/workspace.ts b/site/src/util/workspace.ts index d174072253b76..6c95fe5f59f9f 100644 --- a/site/src/util/workspace.ts +++ b/site/src/util/workspace.ts @@ -199,6 +199,7 @@ export const isWorkspaceOn = (workspace: TypesGen.Workspace): boolean => { return transition === "start" && status === "succeeded" } + export const defaultWorkspaceExtension = (__startDate?: dayjs.Dayjs): TypesGen.PutExtendWorkspaceRequest => { const now = __startDate ? dayjs(__startDate) : dayjs() const NinetyMinutesFromNow = now.add(90, "minutes").utc() diff --git a/site/src/xServices/workspaces/workspacesXService.ts b/site/src/xServices/workspaces/workspacesXService.ts index a5e94c9559a04..d4367b7ad95a3 100644 --- a/site/src/xServices/workspaces/workspacesXService.ts +++ b/site/src/xServices/workspaces/workspacesXService.ts @@ -51,7 +51,7 @@ export const workspacesMachine = createMachine( }, onError: { target: "ready", - actions: "assignGetWorkspacesError", + actions: ["assignGetWorkspacesError", "clearWorkspaces"], }, }, tags: "loading", @@ -70,6 +70,7 @@ export const workspacesMachine = createMachine( getWorkspacesError: (_, event) => event.data, }), clearGetWorkspacesError: (context) => assign({ ...context, getWorkspacesError: undefined }), + clearWorkspaces: (context) => assign({ ...context, workspaces: undefined }), }, services: { getWorkspaces: (context) => API.getWorkspaces(workspaceQueryToFilter(context.filter)), From f1409b2e31593e66a24b2502c745c56e641c0579 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 16:22:40 +0000 Subject: [PATCH 06/22] language --- site/src/pages/WorkspacesPage/WorkspacesPage.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index b64903edac2a1..d360fedb6dc82 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -13,12 +13,18 @@ import { Margins } from "../../components/Margins/Margins" import { Stack } from "../../components/Stack/Stack" import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils" import { workspacesMachine } from "../../xServices/workspaces/workspacesXService" -import { Language, WorkspacesPageView } from "./WorkspacesPageView" +import { WorkspacesPageView } from "./WorkspacesPageView" interface FilterFormValues { query: string } +const Language = { + createWorkspaceButton: "Create workspace", + yourWorkspacesButton: "Your workspaces", + allWorkspacesButton: "All workspaces" +} + export type FilterFormErrors = FormikErrors const WorkspacesPage: FC = () => { @@ -71,8 +77,8 @@ const WorkspacesPage: FC = () => { - Your workspaces - All workspaces + {Language.yourWorkspacesButton} + {Language.allWorkspacesButton}
@@ -81,7 +87,7 @@ const WorkspacesPage: FC = () => { - +
From f8e09e68529ff99a473a7026d71b06cc6cb69045 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 18:58:48 +0000 Subject: [PATCH 07/22] push up what I have --- site/src/pages/WorkspacesPage/WorkspacesPage.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index d360fedb6dc82..23d1503cf6332 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -14,12 +14,14 @@ import { Stack } from "../../components/Stack/Stack" import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils" import { workspacesMachine } from "../../xServices/workspaces/workspacesXService" import { WorkspacesPageView } from "./WorkspacesPageView" +import { CloseDropdown, OpenDropdown } from "../../components/DropdownArrows/DropdownArrows" interface FilterFormValues { query: string } const Language = { + filterName: "Filters", createWorkspaceButton: "Create workspace", yourWorkspacesButton: "Your workspaces", allWorkspacesButton: "All workspaces" @@ -73,10 +75,15 @@ const WorkspacesPage: FC = () => {
- + {Language.yourWorkspacesButton} {Language.allWorkspacesButton} From 424e93352bb6959f08c97afd99fa95025fd88b8f Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 20:00:00 +0000 Subject: [PATCH 08/22] Add name filter to backend --- coderd/database/queries.sql.go | 14 +++++++++++++- coderd/database/queries/workspaces.sql | 6 ++++++ coderd/workspaces.go | 4 ++++ codersdk/workspaces.go | 11 ++++++++++- site/src/api/typesGenerated.ts | 16 +++++++++------- site/src/pages/WorkspacesPage/WorkspacesPage.tsx | 11 +++-------- 6 files changed, 45 insertions(+), 17 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 4d98d9ee2c4a8..07d304d0c3aae 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3509,16 +3509,28 @@ WHERE owner_id = $3 ELSE true END + -- Filter by name + AND CASE + WHEN $4 :: string != '' THEN + name = LOWER($4) + ELSE true + END ` type GetWorkspacesWithFilterParams struct { Deleted bool `db:"deleted" json:"deleted"` OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` + Name string `db:"name" json:"name"` } func (q *sqlQuerier) GetWorkspacesWithFilter(ctx context.Context, arg GetWorkspacesWithFilterParams) ([]Workspace, error) { - rows, err := q.db.QueryContext(ctx, getWorkspacesWithFilter, arg.Deleted, arg.OrganizationID, arg.OwnerID) + rows, err := q.db.QueryContext(ctx, getWorkspacesWithFilter, + arg.Deleted, + arg.OrganizationID, + arg.OwnerID, + arg.Name, + ) if err != nil { return nil, err } diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 000e4e92ce5a9..e406d3c62238a 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -28,6 +28,12 @@ WHERE owner_id = @owner_id ELSE true END + -- Filter by name + AND CASE + WHEN @name :: string != '' THEN + name = LOWER(@name) + ELSE true + END ; -- name: GetWorkspacesByOrganizationIDs :many diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 43c39140a7e53..cef7c0833aa4c 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -137,6 +137,7 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) { // Empty strings mean no filter orgFilter := r.URL.Query().Get("organization_id") ownerFilter := r.URL.Query().Get("owner") + nameFilter := r.URL.Query().Get("name") filter := database.GetWorkspacesWithFilterParams{Deleted: false} if orgFilter != "" { @@ -170,6 +171,9 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) { } filter.OwnerID = userID } + if nameFilter != "" { + filter.Name = nameFilter + } workspaces, err := api.Database.GetWorkspacesWithFilter(r.Context(), filter) if err != nil { diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index cbf94f392da60..07c5819863fbd 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "time" "github.com/google/uuid" @@ -200,7 +201,9 @@ func (c *Client) PutExtendWorkspace(ctx context.Context, id uuid.UUID, req PutEx type WorkspaceFilter struct { OrganizationID uuid.UUID // Owner can be a user_id (uuid), "me", or a username - Owner string + Owner string + Name string + Deleted bool } // asRequestOption returns a function that can be used in (*Client).Request. @@ -214,6 +217,12 @@ func (f WorkspaceFilter) asRequestOption() requestOption { if f.Owner != "" { q.Set("owner", f.Owner) } + if f.Name != "" { + q.Set("name", f.Name) + } + if f.Deleted { + q.Set("deleted", strconv.FormatBool(f.Deleted)) + } r.URL.RawQuery = q.Encode() } } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index d0ce2c3fac371..5e47400ffa776 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -88,7 +88,7 @@ export interface CreateUserRequest { readonly organization_id: string } -// From codersdk/workspaces.go:34:6 +// From codersdk/workspaces.go:35:6 export interface CreateWorkspaceBuildRequest { readonly template_version_id?: string readonly transition: WorkspaceTransition @@ -221,7 +221,7 @@ export interface ProvisionerJobLog { readonly output: string } -// From codersdk/workspaces.go:182:6 +// From codersdk/workspaces.go:183:6 export interface PutExtendWorkspaceRequest { readonly deadline: string } @@ -298,12 +298,12 @@ export interface UpdateUserProfileRequest { readonly username: string } -// From codersdk/workspaces.go:141:6 +// From codersdk/workspaces.go:142:6 export interface UpdateWorkspaceAutostartRequest { readonly schedule?: string } -// From codersdk/workspaces.go:161:6 +// From codersdk/workspaces.go:162:6 export interface UpdateWorkspaceTTLRequest { readonly ttl_ms?: number } @@ -358,7 +358,7 @@ export interface UsersRequest extends Pagination { readonly status?: string } -// From codersdk/workspaces.go:18:6 +// From codersdk/workspaces.go:19:6 export interface Workspace { readonly id: string readonly created_at: string @@ -436,15 +436,17 @@ export interface WorkspaceBuild { readonly deadline: string } -// From codersdk/workspaces.go:64:6 +// From codersdk/workspaces.go:65:6 export interface WorkspaceBuildsRequest extends Pagination { readonly WorkspaceID: string } -// From codersdk/workspaces.go:200:6 +// From codersdk/workspaces.go:201:6 export interface WorkspaceFilter { readonly OrganizationID: string readonly Owner: string + readonly Name: string + readonly Deleted: boolean } // From codersdk/workspaceresources.go:21:6 diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index 23d1503cf6332..37b10589dbc2b 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -9,12 +9,12 @@ import { useMachine } from "@xstate/react" import { FormikErrors, useFormik } from "formik" import { FC, useState } from "react" import { Link as RouterLink } from "react-router-dom" +import { CloseDropdown, OpenDropdown } from "../../components/DropdownArrows/DropdownArrows" import { Margins } from "../../components/Margins/Margins" import { Stack } from "../../components/Stack/Stack" import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils" import { workspacesMachine } from "../../xServices/workspaces/workspacesXService" import { WorkspacesPageView } from "./WorkspacesPageView" -import { CloseDropdown, OpenDropdown } from "../../components/DropdownArrows/DropdownArrows" interface FilterFormValues { query: string @@ -24,7 +24,7 @@ const Language = { filterName: "Filters", createWorkspaceButton: "Create workspace", yourWorkspacesButton: "Your workspaces", - allWorkspacesButton: "All workspaces" + allWorkspacesButton: "All workspaces", } export type FilterFormErrors = FormikErrors @@ -78,12 +78,7 @@ const WorkspacesPage: FC = () => { {Language.filterName} {anchorEl ? : } - + {Language.yourWorkspacesButton} {Language.allWorkspacesButton} From 5dbf5b4bb5d5c4ee17b0005ece98a158ecbc7121 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 20:29:27 +0000 Subject: [PATCH 09/22] Add name filter --- site/src/api/api.test.ts | 6 +++--- site/src/api/api.ts | 11 +++++++---- site/src/api/typesGenerated.ts | 7 +++---- site/src/util/workspace.test.ts | 11 ++++++----- site/src/util/workspace.ts | 23 +++++++++++++++-------- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/site/src/api/api.test.ts b/site/src/api/api.test.ts index 5714c080d384e..083eb177fb6ac 100644 --- a/site/src/api/api.test.ts +++ b/site/src/api/api.test.ts @@ -118,10 +118,10 @@ describe("api.ts", () => { it.each<[TypesGen.WorkspaceFilter | undefined, string]>([ [undefined, "/api/v2/workspaces"], - [{ OrganizationID: "1", Owner: "" }, "/api/v2/workspaces?organization_id=1"], - [{ OrganizationID: "", Owner: "1" }, "/api/v2/workspaces?owner=1"], + [{ organization_id: "1", owner: "" }, "/api/v2/workspaces?organization_id=1"], + [{ organization_id: "", owner: "1" }, "/api/v2/workspaces?owner=1"], - [{ OrganizationID: "1", Owner: "me" }, "/api/v2/workspaces?organization_id=1&owner=me"], + [{ organization_id: "1", owner: "me" }, "/api/v2/workspaces?organization_id=1&owner=me"], ])(`getWorkspacesURL(%p) returns %p`, (filter, expected) => { expect(getWorkspacesURL(filter)).toBe(expected) }) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 4e06ccc5306e8..4d4cc5c970fb8 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -116,11 +116,14 @@ export const getWorkspacesURL = (filter?: TypesGen.WorkspaceFilter): string => { const basePath = "/api/v2/workspaces" const searchParams = new URLSearchParams() - if (filter?.OrganizationID) { - searchParams.append("organization_id", filter.OrganizationID) + if (filter?.organization_id) { + searchParams.append("organization_id", filter.organization_id) } - if (filter?.Owner) { - searchParams.append("owner", filter.Owner) + if (filter?.owner) { + searchParams.append("owner", filter.owner) + } + if (filter?.name) { + searchParams.append("name", filter.name) } const searchString = searchParams.toString() diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 5e47400ffa776..5cfd54be7d23e 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -443,10 +443,9 @@ export interface WorkspaceBuildsRequest extends Pagination { // From codersdk/workspaces.go:201:6 export interface WorkspaceFilter { - readonly OrganizationID: string - readonly Owner: string - readonly Name: string - readonly Deleted: boolean + readonly organization_id?: string + readonly owner?: string + readonly name?: string } // From codersdk/workspaceresources.go:21:6 diff --git a/site/src/util/workspace.test.ts b/site/src/util/workspace.test.ts index a5a2d6752d53e..36a22012a5d0f 100644 --- a/site/src/util/workspace.test.ts +++ b/site/src/util/workspace.test.ts @@ -65,11 +65,12 @@ describe("util > workspace", () => { }) describe("workspaceQueryToFilter", () => { it.each<[string | undefined, TypesGen.WorkspaceFilter]>([ - [undefined, { Owner: "", OrganizationID: "" }], - ["", { Owner: "", OrganizationID: "" }], - ["asdkfvjn", { Owner: "", OrganizationID: "" }], - ["owner:me", { Owner: "me", OrganizationID: "" }], - ["owner:me owner:me2", { Owner: "me", OrganizationID: "" }], + [undefined, {}], + ["", {}], + ["asdkfvjn", { name: "asdkfvjn" }], + ["owner:me", { owner: "me" }], + ["owner:me owner:me2", { owner: "me" }], + ["me/dev", { owner: "me", name: "dev" }], ])(`query=%p, filter=%p`, (query, filter) => { expect(workspaceQueryToFilter(query)).toEqual(filter) }) diff --git a/site/src/util/workspace.ts b/site/src/util/workspace.ts index 6c95fe5f59f9f..f2fba303762c6 100644 --- a/site/src/util/workspace.ts +++ b/site/src/util/workspace.ts @@ -199,6 +199,7 @@ export const isWorkspaceOn = (workspace: TypesGen.Workspace): boolean => { return transition === "start" && status === "succeeded" } +<<<<<<< HEAD export const defaultWorkspaceExtension = (__startDate?: dayjs.Dayjs): TypesGen.PutExtendWorkspaceRequest => { const now = __startDate ? dayjs(__startDate) : dayjs() @@ -209,12 +210,8 @@ export const defaultWorkspaceExtension = (__startDate?: dayjs.Dayjs): TypesGen.P } } -export const workspaceQueryToFilter = (query?: string): WorkspaceFilter => { - const defaultFilter: WorkspaceFilter = { - Owner: "", - OrganizationID: "", - } - +export const workspaceQueryToFilter = (query?: string): TypesGen.WorkspaceFilter => { + const defaultFilter: TypesGen.WorkspaceFilter = {} const preparedQuery = query?.replace(/ +/g, " ") if (!preparedQuery) { @@ -226,10 +223,20 @@ export const workspaceQueryToFilter = (query?: string): WorkspaceFilter => { const [key, val] = part.split(":") if (key === "owner") { return { - Owner: val, - OrganizationID: "", + owner: val, + } + } + + const [username, name] = part.split("/") + if (username && name) { + return { + owner: username, + name: name, } } + return { + name: part, + } } return defaultFilter From 2912d038889ca09d6783b37183ec5d463f399ec8 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 20:33:24 +0000 Subject: [PATCH 10/22] add backend --- coderd/workspaces.go | 19 ++++++------------- codersdk/workspaces.go | 11 +++-------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index cef7c0833aa4c..744dbd553df45 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -142,13 +142,9 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) { filter := database.GetWorkspacesWithFilterParams{Deleted: false} if orgFilter != "" { orgID, err := uuid.Parse(orgFilter) - if err != nil { - httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ - Message: fmt.Sprintf("organization_id must be a uuid: %s", err.Error()), - }) - return + if err == nil { + filter.OrganizationID = orgID } - filter.OrganizationID = orgID } if ownerFilter == "me" { filter.OwnerID = apiKey.UserID @@ -161,15 +157,12 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) { Username: ownerFilter, Email: ownerFilter, }) - if err != nil { - httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ - Message: "owner must be a uuid or username", - }) - return + if err == nil { + filter.OwnerID = user.ID } - userID = user.ID + } else { + filter.OwnerID = userID } - filter.OwnerID = userID } if nameFilter != "" { filter.Name = nameFilter diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index 07c5819863fbd..dbe3ba1b8574c 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "net/http" - "strconv" "time" "github.com/google/uuid" @@ -199,11 +198,10 @@ func (c *Client) PutExtendWorkspace(ctx context.Context, id uuid.UUID, req PutEx } type WorkspaceFilter struct { - OrganizationID uuid.UUID + OrganizationID uuid.UUID `json:"organization_id,omitempty"` // Owner can be a user_id (uuid), "me", or a username - Owner string - Name string - Deleted bool + Owner string `json:"owner,omitempty"` + Name string `json:"name,omitempty"` } // asRequestOption returns a function that can be used in (*Client).Request. @@ -220,9 +218,6 @@ func (f WorkspaceFilter) asRequestOption() requestOption { if f.Name != "" { q.Set("name", f.Name) } - if f.Deleted { - q.Set("deleted", strconv.FormatBool(f.Deleted)) - } r.URL.RawQuery = q.Encode() } } From 9ea5c14360e4e2e3067ff3b057d36c9a16bc1269 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 21:13:55 +0000 Subject: [PATCH 11/22] fix --- site/src/util/workspace.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/site/src/util/workspace.ts b/site/src/util/workspace.ts index f2fba303762c6..a102d704bd9e0 100644 --- a/site/src/util/workspace.ts +++ b/site/src/util/workspace.ts @@ -199,8 +199,6 @@ export const isWorkspaceOn = (workspace: TypesGen.Workspace): boolean => { return transition === "start" && status === "succeeded" } -<<<<<<< HEAD - export const defaultWorkspaceExtension = (__startDate?: dayjs.Dayjs): TypesGen.PutExtendWorkspaceRequest => { const now = __startDate ? dayjs(__startDate) : dayjs() const NinetyMinutesFromNow = now.add(90, "minutes").utc() From 93a7d3cfffaabc8b8c0a571c5a82f94939b75894 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Thu, 2 Jun 2022 16:16:28 -0500 Subject: [PATCH 12/22] Apply suggestions from code review Co-authored-by: G r e y --- site/src/util/workspace.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/util/workspace.test.ts b/site/src/util/workspace.test.ts index 36a22012a5d0f..04712a06a7dbb 100644 --- a/site/src/util/workspace.test.ts +++ b/site/src/util/workspace.test.ts @@ -71,6 +71,7 @@ describe("util > workspace", () => { ["owner:me", { owner: "me" }], ["owner:me owner:me2", { owner: "me" }], ["me/dev", { owner: "me", name: "dev" }], + [" key:val owner:me ", { owner: "me" }], ])(`query=%p, filter=%p`, (query, filter) => { expect(workspaceQueryToFilter(query)).toEqual(filter) }) From 79e71f12e7a68f46bb954d682abc2a58e754c683 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 21:21:45 +0000 Subject: [PATCH 13/22] fmt --- site/src/api/typesGenerated.ts | 14 +++++++------- site/src/util/workspace.test.ts | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 5cfd54be7d23e..4a0f3e267027b 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -88,7 +88,7 @@ export interface CreateUserRequest { readonly organization_id: string } -// From codersdk/workspaces.go:35:6 +// From codersdk/workspaces.go:34:6 export interface CreateWorkspaceBuildRequest { readonly template_version_id?: string readonly transition: WorkspaceTransition @@ -221,7 +221,7 @@ export interface ProvisionerJobLog { readonly output: string } -// From codersdk/workspaces.go:183:6 +// From codersdk/workspaces.go:182:6 export interface PutExtendWorkspaceRequest { readonly deadline: string } @@ -298,12 +298,12 @@ export interface UpdateUserProfileRequest { readonly username: string } -// From codersdk/workspaces.go:142:6 +// From codersdk/workspaces.go:141:6 export interface UpdateWorkspaceAutostartRequest { readonly schedule?: string } -// From codersdk/workspaces.go:162:6 +// From codersdk/workspaces.go:161:6 export interface UpdateWorkspaceTTLRequest { readonly ttl_ms?: number } @@ -358,7 +358,7 @@ export interface UsersRequest extends Pagination { readonly status?: string } -// From codersdk/workspaces.go:19:6 +// From codersdk/workspaces.go:18:6 export interface Workspace { readonly id: string readonly created_at: string @@ -436,12 +436,12 @@ export interface WorkspaceBuild { readonly deadline: string } -// From codersdk/workspaces.go:65:6 +// From codersdk/workspaces.go:64:6 export interface WorkspaceBuildsRequest extends Pagination { readonly WorkspaceID: string } -// From codersdk/workspaces.go:201:6 +// From codersdk/workspaces.go:200:6 export interface WorkspaceFilter { readonly organization_id?: string readonly owner?: string diff --git a/site/src/util/workspace.test.ts b/site/src/util/workspace.test.ts index 36a22012a5d0f..036b84d5b37d3 100644 --- a/site/src/util/workspace.test.ts +++ b/site/src/util/workspace.test.ts @@ -1,7 +1,7 @@ import dayjs from "dayjs" import * as TypesGen from "../api/typesGenerated" import * as Mocks from "../testHelpers/entities" -import { isWorkspaceOn, workspaceQueryToFilter, defaultWorkspaceExtension } from "./workspace" +import { defaultWorkspaceExtension, isWorkspaceOn, workspaceQueryToFilter } from "./workspace" describe("util > workspace", () => { describe("isWorkspaceOn", () => { @@ -61,8 +61,8 @@ describe("util > workspace", () => { ], ])(`defaultWorkspaceExtension(%p) returns %p`, (startTime, request) => { expect(defaultWorkspaceExtension(dayjs(startTime))).toEqual(request) - }) }) + }) describe("workspaceQueryToFilter", () => { it.each<[string | undefined, TypesGen.WorkspaceFilter]>([ [undefined, {}], From 6b2999db78cf8aa40db21fe729800ce673c7356c Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 21:29:08 +0000 Subject: [PATCH 14/22] trim query --- site/src/util/workspace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/util/workspace.ts b/site/src/util/workspace.ts index a102d704bd9e0..93ec6d05ef1c8 100644 --- a/site/src/util/workspace.ts +++ b/site/src/util/workspace.ts @@ -210,7 +210,7 @@ export const defaultWorkspaceExtension = (__startDate?: dayjs.Dayjs): TypesGen.P export const workspaceQueryToFilter = (query?: string): TypesGen.WorkspaceFilter => { const defaultFilter: TypesGen.WorkspaceFilter = {} - const preparedQuery = query?.replace(/ +/g, " ") + const preparedQuery = query?.trim().replace(/ +/g, " ") if (!preparedQuery) { return defaultFilter From 57fe5dbba300be35ce4ceda280ce02e5b7f4c52f Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 21:49:27 +0000 Subject: [PATCH 15/22] skip invalid key pairs --- site/src/util/workspace.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/site/src/util/workspace.ts b/site/src/util/workspace.ts index 93ec6d05ef1c8..44dec857ab3d8 100644 --- a/site/src/util/workspace.ts +++ b/site/src/util/workspace.ts @@ -219,10 +219,14 @@ export const workspaceQueryToFilter = (query?: string): TypesGen.WorkspaceFilter for (const part of parts) { const [key, val] = part.split(":") - if (key === "owner") { - return { - owner: val, + if (key && val) { + if (key === "owner") { + return { + owner: val, + } } + // skip invalid key pairs + continue } const [username, name] = part.split("/") From 0a5d97240a572c9cd39272193442acd9a9f273ea Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 21:59:50 +0000 Subject: [PATCH 16/22] fix pq type --- coderd/database/queries/workspaces.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index e406d3c62238a..ee40fb0431427 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -30,7 +30,7 @@ WHERE END -- Filter by name AND CASE - WHEN @name :: string != '' THEN + WHEN @name :: text != '' THEN name = LOWER(@name) ELSE true END From 4aea7a0a1faf03113fe6c4a4d3132c691f7b09d7 Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 22:10:00 +0000 Subject: [PATCH 17/22] fix tests --- coderd/database/databasefake/databasefake.go | 3 ++ coderd/workspaces_test.go | 30 ++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 88c17101f833c..2a97dd0d1419e 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -328,6 +328,9 @@ func (q *fakeQuerier) GetWorkspacesWithFilter(_ context.Context, arg database.Ge if !arg.Deleted && workspace.Deleted { continue } + if arg.Name != "" && workspace.Name != arg.Name { + continue + } workspaces = append(workspaces, workspace) } diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 392007dcdf18a..1dd4a8b1f5362 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -268,6 +268,36 @@ func TestWorkspacesByOwner(t *testing.T) { require.NoError(t, err) require.Len(t, workspaces, 1) }) + + t.Run("ListName", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) + user := coderdtest.CreateFirstUser(t, client) + + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + w := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + + // Create noise workspace that should be filtered out + _ = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + + // Use name filter + workspaces, err := client.Workspaces(context.Background(), codersdk.WorkspaceFilter{ + Name: w.Name, + }) + require.NoError(t, err) + require.Len(t, workspaces, 1) + + // Create same name workspace that should be included + _ = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) { cwr.Name = w.Name }) + + workspaces, err = client.Workspaces(context.Background(), codersdk.WorkspaceFilter{ + Name: w.Name, + }) + require.NoError(t, err) + require.Len(t, workspaces, 2) + }) } func TestWorkspaceByOwnerAndName(t *testing.T) { From 0dd555273d0d32b8c6a1fcb9090723ec7177a9ab Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 22:10:44 +0000 Subject: [PATCH 18/22] make gen --- coderd/database/queries.sql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 07d304d0c3aae..e048112181a8f 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3511,7 +3511,7 @@ WHERE END -- Filter by name AND CASE - WHEN $4 :: string != '' THEN + WHEN $4 :: text != '' THEN name = LOWER($4) ELSE true END From 5d0d9bbc91cab32e6e40a47615e8a7f1a1d5408c Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 22:15:50 +0000 Subject: [PATCH 19/22] fix test --- coderd/workspaces_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 1dd4a8b1f5362..9ee42fd12ea81 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -290,7 +290,8 @@ func TestWorkspacesByOwner(t *testing.T) { require.Len(t, workspaces, 1) // Create same name workspace that should be included - _ = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) { cwr.Name = w.Name }) + other := coderdtest.CreateAnotherUser(t, client, user.OrganizationID) + _ = coderdtest.CreateWorkspace(t, other, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) { cwr.Name = w.Name }) workspaces, err = client.Workspaces(context.Background(), codersdk.WorkspaceFilter{ Name: w.Name, From 59e7d5ef2ba1d09392f749aa6521bded734acaca Mon Sep 17 00:00:00 2001 From: Garrett Date: Thu, 2 Jun 2022 23:58:45 +0000 Subject: [PATCH 20/22] flicker less --- site/src/pages/WorkspacesPage/WorkspacesPageView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index bc49893e2e94b..2a7c8a22b4a40 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -49,7 +49,7 @@ export const WorkspacesPageView: FC = ({ loading, works - {loading && } + {!workspaces && loading && } {workspaces && workspaces.length === 0 && ( From 912b65c4609c07ba34a910775f615c2e8d6b19be Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Thu, 2 Jun 2022 21:48:08 -0400 Subject: [PATCH 21/22] some styling ideas (#2010) --- .../pages/WorkspacesPage/WorkspacesPage.tsx | 104 +++++++++++++----- 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index 37b10589dbc2b..00ae3bff7bcf3 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -1,10 +1,13 @@ import Button from "@material-ui/core/Button" +import Fade from "@material-ui/core/Fade" +import InputAdornment from "@material-ui/core/InputAdornment" import Link from "@material-ui/core/Link" import Menu from "@material-ui/core/Menu" import MenuItem from "@material-ui/core/MenuItem" import { makeStyles } from "@material-ui/core/styles" import TextField from "@material-ui/core/TextField" import AddCircleOutline from "@material-ui/icons/AddCircleOutline" +import SearchIcon from "@material-ui/icons/Search" import { useMachine } from "@xstate/react" import { FormikErrors, useFormik } from "formik" import { FC, useState } from "react" @@ -70,45 +73,96 @@ const WorkspacesPage: FC = () => { } return ( - <> - -
- - - + + + + + ), + }} + /> + + + {Language.yourWorkspacesButton} {Language.allWorkspacesButton} - -
- - - - - - -
- - -
- + + + + + + + + ) } const useStyles = makeStyles((theme) => ({ - actions: { + workspacesHeaderContainer: { marginTop: theme.spacing(3), marginBottom: theme.spacing(3), - display: "flex", justifyContent: "space-between", - height: theme.spacing(6), + }, + filterColumn: { + width: "60%", + cursor: "text", + }, + filterContainer: { + border: `1px solid ${theme.palette.divider}`, + borderRadius: "6px", + }, + filterForm: { + width: "100%", + }, + buttonRoot: { + border: "none", + borderRight: `1px solid ${theme.palette.divider}`, + borderRadius: "6px 0px 0px 6px", + }, + textFieldRoot: { + margin: "0px", + "& fieldset": { + border: "none", + }, }, })) From 30a17e9d8a032566e99ac10ea931b8890f25b06b Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 3 Jun 2022 17:09:31 +0000 Subject: [PATCH 22/22] fix lower comparison --- coderd/database/queries.sql.go | 2 +- coderd/database/queries/workspaces.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index e048112181a8f..5aaf9d8f9b389 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3512,7 +3512,7 @@ WHERE -- Filter by name AND CASE WHEN $4 :: text != '' THEN - name = LOWER($4) + LOWER(name) = LOWER($4) ELSE true END ` diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index ee40fb0431427..291f04c96da7a 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -31,7 +31,7 @@ WHERE -- Filter by name AND CASE WHEN @name :: text != '' THEN - name = LOWER(@name) + LOWER(name) = LOWER(@name) ELSE true END ;