mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: filter for running workspaces (#4157)
* Refactor workspaces xservice * Remove layout comment * Format * Add comments * Add running workspaces filter to frontend * Start on backend - add status to filter * Update sql and add test - wip * Attempt to unconvert status for easier querying * Fix syntax * Join jobs table, untested * sql * Add Status to GetAuthorizedWorkspaces * Update job tests to have canceled time * fmt * add status filter to database fake Co-authored-by: Colin Adler <colin1adler@gmail.com>
This commit is contained in:
co-authored by
Colin Adler
parent
aefb477e21
commit
62357084ba
@@ -500,6 +500,7 @@ export interface ProvisionerJob {
|
||||
readonly created_at: string
|
||||
readonly started_at?: string
|
||||
readonly completed_at?: string
|
||||
readonly canceled_at?: string
|
||||
readonly error?: string
|
||||
readonly status: ProvisionerJobStatus
|
||||
readonly worker_id?: string
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMachine } from "@xstate/react"
|
||||
import { FC, useEffect } from "react"
|
||||
import { FC } from "react"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
import { useSearchParams } from "react-router-dom"
|
||||
import { workspaceFilterQuery } from "util/filters"
|
||||
@@ -9,25 +9,15 @@ import { WorkspacesPageView } from "./WorkspacesPageView"
|
||||
|
||||
const WorkspacesPage: FC = () => {
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
const filter = searchParams.get("filter")
|
||||
const defaultFilter = filter ?? workspaceFilterQuery.me
|
||||
const filter = searchParams.get("filter") ?? workspaceFilterQuery.me
|
||||
const [workspacesState, send] = useMachine(workspacesMachine, {
|
||||
context: {
|
||||
filter: defaultFilter,
|
||||
filter,
|
||||
},
|
||||
})
|
||||
|
||||
const { workspaceRefs } = workspacesState.context
|
||||
|
||||
// On page load, populate the table with workspaces
|
||||
useEffect(() => {
|
||||
send({
|
||||
type: "GET_WORKSPACES",
|
||||
query: defaultFilter,
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
|
||||
@@ -18,6 +18,7 @@ export const Language = {
|
||||
pageTitle: "Workspaces",
|
||||
yourWorkspacesButton: "Your workspaces",
|
||||
allWorkspacesButton: "All workspaces",
|
||||
runningWorkspacesButton: "Running workspaces",
|
||||
createANewWorkspace: `Create a new workspace from a `,
|
||||
template: "Template",
|
||||
}
|
||||
@@ -35,6 +36,10 @@ export const WorkspacesPageView: FC<
|
||||
const presetFilters = [
|
||||
{ query: workspaceFilterQuery.me, name: Language.yourWorkspacesButton },
|
||||
{ query: workspaceFilterQuery.all, name: Language.allWorkspacesButton },
|
||||
{
|
||||
query: workspaceFilterQuery.running,
|
||||
name: Language.runningWorkspacesButton,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,6 +13,7 @@ export const queryToFilter = (
|
||||
export const workspaceFilterQuery = {
|
||||
me: "owner:me",
|
||||
all: "",
|
||||
running: "status:running",
|
||||
}
|
||||
|
||||
export const userFilterQuery = {
|
||||
|
||||
@@ -209,12 +209,11 @@ interface WorkspacesContext {
|
||||
}
|
||||
|
||||
type WorkspacesEvent =
|
||||
| { type: "GET_WORKSPACES"; query: string }
|
||||
| { type: "GET_WORKSPACES"; query?: string }
|
||||
| { type: "UPDATE_VERSION"; workspaceId: string }
|
||||
|
||||
export const workspacesMachine = createMachine(
|
||||
{
|
||||
predictableActionArguments: true,
|
||||
tsTypes: {} as import("./workspacesXService.typegen").Typegen1,
|
||||
schema: {
|
||||
context: {} as WorkspacesContext,
|
||||
@@ -223,21 +222,28 @@ export const workspacesMachine = createMachine(
|
||||
getWorkspaces: {
|
||||
data: TypesGen.Workspace[]
|
||||
}
|
||||
updateWorkspaceRefs: {
|
||||
data: {
|
||||
refsToKeep: WorkspaceItemMachineRef[]
|
||||
newWorkspaces: TypesGen.Workspace[]
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
predictableActionArguments: true,
|
||||
id: "workspacesState",
|
||||
on: {
|
||||
GET_WORKSPACES: {
|
||||
actions: "assignFilter",
|
||||
target: "gettingWorkspaces",
|
||||
target: ".gettingWorkspaces",
|
||||
internal: false,
|
||||
},
|
||||
UPDATE_VERSION: {
|
||||
actions: "triggerUpdateVersion",
|
||||
},
|
||||
},
|
||||
initial: "idle",
|
||||
initial: "gettingWorkspaces",
|
||||
states: {
|
||||
idle: {},
|
||||
gettingWorkspaces: {
|
||||
entry: "clearGetWorkspacesError",
|
||||
invoke: {
|
||||
@@ -245,24 +251,39 @@ export const workspacesMachine = createMachine(
|
||||
id: "getWorkspaces",
|
||||
onDone: [
|
||||
{
|
||||
target: "waitToRefreshWorkspaces",
|
||||
actions: ["assignWorkspaceRefs"],
|
||||
actions: "assignWorkspaceRefs",
|
||||
cond: "isEmpty",
|
||||
target: "waitToRefreshWorkspaces",
|
||||
},
|
||||
{
|
||||
target: "updatingWorkspaceRefs",
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
actions: "assignGetWorkspacesError",
|
||||
target: "waitToRefreshWorkspaces",
|
||||
actions: ["updateWorkspaceRefs"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
updatingWorkspaceRefs: {
|
||||
invoke: {
|
||||
src: "updateWorkspaceRefs",
|
||||
id: "updateWorkspaceRefs",
|
||||
onDone: [
|
||||
{
|
||||
actions: "assignUpdatedWorkspaceRefs",
|
||||
target: "waitToRefreshWorkspaces",
|
||||
},
|
||||
],
|
||||
onError: {
|
||||
target: "waitToRefreshWorkspaces",
|
||||
actions: ["assignGetWorkspacesError"],
|
||||
},
|
||||
},
|
||||
},
|
||||
waitToRefreshWorkspaces: {
|
||||
after: {
|
||||
5000: "gettingWorkspaces",
|
||||
"5000": {
|
||||
target: "gettingWorkspaces",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -279,7 +300,7 @@ export const workspacesMachine = createMachine(
|
||||
}),
|
||||
}),
|
||||
assignFilter: assign({
|
||||
filter: (_, event) => event.query,
|
||||
filter: (context, event) => event.query ?? context.filter,
|
||||
}),
|
||||
assignGetWorkspacesError: assign({
|
||||
getWorkspacesError: (_, event) => event.data,
|
||||
@@ -297,55 +318,48 @@ export const workspacesMachine = createMachine(
|
||||
|
||||
workspaceRef.send("UPDATE_VERSION")
|
||||
},
|
||||
// Opened discussion on XState https://github.com/statelyai/xstate/discussions/3406
|
||||
updateWorkspaceRefs: assign({
|
||||
workspaceRefs: (context, event) => {
|
||||
let workspaceRefs = context.workspaceRefs
|
||||
|
||||
if (!workspaceRefs) {
|
||||
throw new Error("No workspaces loaded.")
|
||||
}
|
||||
|
||||
// Update the existent workspaces or create the new ones
|
||||
for (const data of event.data) {
|
||||
const ref = workspaceRefs.find((ref) => ref.id === data.id)
|
||||
|
||||
if (!ref) {
|
||||
workspaceRefs.push(
|
||||
spawn(workspaceItemMachine.withContext({ data }), data.id),
|
||||
)
|
||||
} else {
|
||||
ref.send({ type: "UPDATE_DATA", data })
|
||||
}
|
||||
}
|
||||
|
||||
// Remove workspaces that were deleted
|
||||
for (const ref of workspaceRefs) {
|
||||
const refData = event.data.find(
|
||||
(workspaceData) => workspaceData.id === ref.id,
|
||||
)
|
||||
|
||||
// If there is no refData, it is because the workspace was deleted
|
||||
if (!refData) {
|
||||
// Stop the actor before remove it from the array
|
||||
if (ref.stop) {
|
||||
ref.stop()
|
||||
}
|
||||
|
||||
// Remove ref from the array
|
||||
workspaceRefs = workspaceRefs.filter(
|
||||
(oldRef) => oldRef.id !== ref.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return workspaceRefs
|
||||
assignUpdatedWorkspaceRefs: assign({
|
||||
workspaceRefs: (_, event) => {
|
||||
const newWorkspaceRefs = event.data.newWorkspaces.map((workspace) =>
|
||||
spawn(
|
||||
workspaceItemMachine.withContext({ data: workspace }),
|
||||
workspace.id,
|
||||
),
|
||||
)
|
||||
return event.data.refsToKeep.concat(newWorkspaceRefs)
|
||||
},
|
||||
}),
|
||||
},
|
||||
services: {
|
||||
getWorkspaces: (context) =>
|
||||
API.getWorkspaces(queryToFilter(context.filter)),
|
||||
updateWorkspaceRefs: (context, event) => {
|
||||
const refsToKeep: WorkspaceItemMachineRef[] = []
|
||||
context.workspaceRefs?.forEach((ref) => {
|
||||
const matchingWorkspace = event.data.find(
|
||||
(workspace) => ref.id === workspace.id,
|
||||
)
|
||||
if (matchingWorkspace) {
|
||||
// if a workspace machine reference describes a workspace that has not been deleted,
|
||||
// update its data and mark it as a refToKeep
|
||||
ref.send({ type: "UPDATE_DATA", data: matchingWorkspace })
|
||||
refsToKeep.push(ref)
|
||||
} else {
|
||||
// if it describes a workspace that has been deleted, stop the machine
|
||||
ref.stop && ref.stop()
|
||||
}
|
||||
})
|
||||
|
||||
const newWorkspaces = event.data.filter(
|
||||
(workspace) =>
|
||||
!context.workspaceRefs?.find((ref) => ref.id === workspace.id),
|
||||
)
|
||||
|
||||
return Promise.resolve({
|
||||
refsToKeep,
|
||||
newWorkspaces,
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user