mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: Make the workspace URL pretty (#2101)
This adds the `@username/workspacename` format to the workspace page!
This commit is contained in:
+17
-19
@@ -65,25 +65,6 @@ export const AppRouter: FC = () => (
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path=":workspace">
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<AuthAndFrame>
|
||||
<WorkspacePage />
|
||||
</AuthAndFrame>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="schedule"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<WorkspaceSchedulePage />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
</Route>
|
||||
|
||||
<Route path="templates">
|
||||
@@ -142,6 +123,23 @@ export const AppRouter: FC = () => (
|
||||
|
||||
<Route path="/@:username">
|
||||
<Route path=":workspace">
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<AuthAndFrame>
|
||||
<WorkspacePage />
|
||||
</AuthAndFrame>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="schedule"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<WorkspaceSchedulePage />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="terminal"
|
||||
element={
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import List from "@material-ui/core/List"
|
||||
import ListItem from "@material-ui/core/ListItem"
|
||||
import { fade, makeStyles } from "@material-ui/core/styles"
|
||||
import { NavLink } from "react-router-dom"
|
||||
import { NavLink, useLocation } from "react-router-dom"
|
||||
import * as TypesGen from "../../api/typesGenerated"
|
||||
import { navHeight } from "../../theme/constants"
|
||||
import { combineClasses } from "../../util/combineClasses"
|
||||
import { Logo } from "../Icons/Logo"
|
||||
import { UserDropdown } from "../UserDropdown/UsersDropdown"
|
||||
|
||||
@@ -20,6 +21,7 @@ export const Language = {
|
||||
|
||||
export const NavbarView: React.FC<NavbarViewProps> = ({ user, onSignOut }) => {
|
||||
const styles = useStyles()
|
||||
const location = useLocation()
|
||||
return (
|
||||
<nav className={styles.root}>
|
||||
<List className={styles.fixed}>
|
||||
@@ -29,7 +31,10 @@ export const NavbarView: React.FC<NavbarViewProps> = ({ user, onSignOut }) => {
|
||||
</NavLink>
|
||||
</ListItem>
|
||||
<ListItem button className={styles.item}>
|
||||
<NavLink className={styles.link} to="/workspaces">
|
||||
<NavLink
|
||||
className={combineClasses([styles.link, location.pathname.startsWith("/@") && "active"])}
|
||||
to="/workspaces"
|
||||
>
|
||||
{Language.workspaces}
|
||||
</NavLink>
|
||||
</ListItem>
|
||||
|
||||
@@ -27,7 +27,10 @@ import { WorkspacePage } from "./WorkspacePage"
|
||||
|
||||
// It renders the workspace page and waits for it be loaded
|
||||
const renderWorkspacePage = async () => {
|
||||
renderWithAuth(<WorkspacePage />, { route: `/workspaces/${MockWorkspace.id}`, path: "/workspaces/:workspace" })
|
||||
renderWithAuth(<WorkspacePage />, {
|
||||
route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}`,
|
||||
path: "/@:username/:workspace",
|
||||
})
|
||||
await screen.findByText(MockWorkspace.name)
|
||||
}
|
||||
|
||||
@@ -47,7 +50,7 @@ const testButton = async (label: string, actionMock: jest.SpyInstance) => {
|
||||
|
||||
const testStatus = async (mock: Workspace, label: string) => {
|
||||
server.use(
|
||||
rest.get(`/api/v2/workspaces/${MockWorkspace.id}`, (req, res, ctx) => {
|
||||
rest.get(`/api/v2/users/:username/workspace/:workspaceName`, (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(mock))
|
||||
}),
|
||||
)
|
||||
@@ -87,7 +90,7 @@ describe("Workspace Page", () => {
|
||||
})
|
||||
it("requests a start job when the user presses Start", async () => {
|
||||
server.use(
|
||||
rest.get(`/api/v2/workspaces/${MockWorkspace.id}`, (req, res, ctx) => {
|
||||
rest.get(`/api/v2/users/:userId/workspace/:workspaceName`, (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(MockStoppedWorkspace))
|
||||
}),
|
||||
)
|
||||
@@ -98,7 +101,7 @@ describe("Workspace Page", () => {
|
||||
})
|
||||
it("requests cancellation when the user presses Cancel", async () => {
|
||||
server.use(
|
||||
rest.get(`/api/v2/workspaces/${MockWorkspace.id}`, (req, res, ctx) => {
|
||||
rest.get(`/api/v2/users/:userId/workspace/:workspaceName`, (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(MockStartingWorkspace))
|
||||
}),
|
||||
)
|
||||
@@ -110,7 +113,7 @@ describe("Workspace Page", () => {
|
||||
it("requests a template when the user presses Update", async () => {
|
||||
const getTemplateMock = jest.spyOn(api, "getTemplate").mockResolvedValueOnce(MockTemplate)
|
||||
server.use(
|
||||
rest.get(`/api/v2/workspaces/${MockWorkspace.id}`, (req, res, ctx) => {
|
||||
rest.get(`/api/v2/users/:userId/workspace/:workspaceName`, (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(MockOutdatedWorkspace))
|
||||
}),
|
||||
)
|
||||
@@ -159,7 +162,10 @@ describe("Workspace Page", () => {
|
||||
|
||||
describe("Resources", () => {
|
||||
it("shows the status of each agent in each resource", async () => {
|
||||
renderWithAuth(<WorkspacePage />, { route: `/workspaces/${MockWorkspace.id}`, path: "/workspaces/:workspace" })
|
||||
renderWithAuth(<WorkspacePage />, {
|
||||
route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}`,
|
||||
path: "/@:username/:workspace",
|
||||
})
|
||||
const agent1Names = await screen.findAllByText(MockWorkspaceAgent.name)
|
||||
expect(agent1Names.length).toEqual(2)
|
||||
const agent2Names = await screen.findAllByText(MockWorkspaceAgentDisconnected.name)
|
||||
|
||||
@@ -14,9 +14,10 @@ import { workspaceMachine } from "../../xServices/workspace/workspaceXService"
|
||||
import { workspaceScheduleBannerMachine } from "../../xServices/workspaceSchedule/workspaceScheduleBannerXService"
|
||||
|
||||
export const WorkspacePage: React.FC = () => {
|
||||
const { workspace: workspaceQueryParam } = useParams()
|
||||
const { username: usernameQueryParam, workspace: workspaceQueryParam } = useParams()
|
||||
const navigate = useNavigate()
|
||||
const workspaceId = firstOrItem(workspaceQueryParam, null)
|
||||
const username = firstOrItem(usernameQueryParam, null)
|
||||
const workspaceName = firstOrItem(workspaceQueryParam, null)
|
||||
|
||||
const [workspaceState, workspaceSend] = useMachine(workspaceMachine)
|
||||
const { workspace, resources, getWorkspaceError, getResourcesError, builds } = workspaceState.context
|
||||
@@ -28,8 +29,8 @@ export const WorkspacePage: React.FC = () => {
|
||||
* workspaceSend should not change.
|
||||
*/
|
||||
useEffect(() => {
|
||||
workspaceId && workspaceSend({ type: "GET_WORKSPACE", workspaceId })
|
||||
}, [workspaceId, workspaceSend])
|
||||
username && workspaceName && workspaceSend({ type: "GET_WORKSPACE", username, workspaceName })
|
||||
}, [username, workspaceName, workspaceSend])
|
||||
|
||||
if (workspaceState.matches("error")) {
|
||||
return <ErrorSummary error={getWorkspaceError} />
|
||||
|
||||
@@ -74,7 +74,7 @@ export const WorkspacesPageView: FC<WorkspacesPageViewProps> = ({ loading, works
|
||||
<AvatarData
|
||||
title={workspace.name}
|
||||
subtitle={workspace.owner_name}
|
||||
link={`/workspaces/${workspace.id}`}
|
||||
link={`/@${workspace.owner_name}/${workspace.name}`}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{workspace.template_name}</TableCell>
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface WorkspaceContext {
|
||||
}
|
||||
|
||||
export type WorkspaceEvent =
|
||||
| { type: "GET_WORKSPACE"; workspaceId: string }
|
||||
| { type: "GET_WORKSPACE"; workspaceName: string; username: string }
|
||||
| { type: "START" }
|
||||
| { type: "STOP" }
|
||||
| { type: "ASK_DELETE" }
|
||||
@@ -431,7 +431,7 @@ export const workspaceMachine = createMachine(
|
||||
},
|
||||
services: {
|
||||
getWorkspace: async (_, event) => {
|
||||
return await API.getWorkspace(event.workspaceId)
|
||||
return await API.getWorkspaceByOwnerAndName(event.username, event.workspaceName)
|
||||
},
|
||||
getTemplate: async (context) => {
|
||||
if (context.workspace) {
|
||||
@@ -470,7 +470,7 @@ export const workspaceMachine = createMachine(
|
||||
},
|
||||
refreshWorkspace: async (context) => {
|
||||
if (context.workspace) {
|
||||
return await API.getWorkspace(context.workspace.id)
|
||||
return await API.getWorkspaceByOwnerAndName(context.workspace.owner_name, context.workspace.name)
|
||||
} else {
|
||||
throw Error("Cannot refresh workspace without id")
|
||||
}
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ const config: Configuration = {
|
||||
port: process.env.PORT || 8080,
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://localhost:3000",
|
||||
target: "https://dev.coder.com",
|
||||
ws: true,
|
||||
secure: false,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user