diff --git a/site/src/modules/tasks/TasksSidebar/TasksSidebar.tsx b/site/src/modules/tasks/TasksSidebar/TasksSidebar.tsx index 08ab6c891b..7f1685629c 100644 --- a/site/src/modules/tasks/TasksSidebar/TasksSidebar.tsx +++ b/site/src/modules/tasks/TasksSidebar/TasksSidebar.tsx @@ -7,6 +7,7 @@ import { DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, } from "components/DropdownMenu/DropdownMenu"; import { CoderIcon } from "components/Icons/CoderIcon"; @@ -21,7 +22,13 @@ import { } from "components/Tooltip/Tooltip"; import { useAuthenticated } from "hooks"; import { useSearchParamsKey } from "hooks/useSearchParamsKey"; -import { EditIcon, EllipsisIcon, PanelLeftIcon, TrashIcon } from "lucide-react"; +import { + EditIcon, + EllipsisIcon, + PanelLeftIcon, + Share2Icon, + TrashIcon, +} from "lucide-react"; import { type FC, useState } from "react"; import { useQuery } from "react-query"; import { Link as RouterLink, useNavigate, useParams } from "react-router"; @@ -227,6 +234,15 @@ const TaskSidebarMenuItem: FC = ({ task }) => { + + + + Share workspace + + + { @@ -235,7 +251,7 @@ const TaskSidebarMenuItem: FC = ({ task }) => { }} > - Delete + Delete… diff --git a/site/src/pages/TaskPage/TaskPage.tsx b/site/src/pages/TaskPage/TaskPage.tsx index 0808fe851d..0863f980c2 100644 --- a/site/src/pages/TaskPage/TaskPage.tsx +++ b/site/src/pages/TaskPage/TaskPage.tsx @@ -5,6 +5,7 @@ import { workspaceBuildParameters } from "api/queries/workspaceBuilds"; import { startWorkspace, workspaceByOwnerAndName, + workspacePermissions, } from "api/queries/workspaces"; import type { Workspace, @@ -78,6 +79,7 @@ const TaskPage = () => { return state.error ? false : 5_000; }, }); + const { data: permissions } = useQuery(workspacePermissions(workspace)); const refetch = taskQuery.error ? taskQuery.refetch : workspaceQuery.refetch; const error = taskQuery.error ?? workspaceQuery.error; const waitingStatuses: WorkspaceStatus[] = ["starting", "pending"]; @@ -198,7 +200,11 @@ const TaskPage = () => { {pageTitle(task.display_name)} - + {content} = ({ task, workspace }) => { +export const TaskTopbar: FC = ({ + task, + workspace, + canUpdatePermissions, +}) => { return (
@@ -57,8 +66,8 @@ export const TaskTopbar: FC = ({ task, workspace }) => { @@ -70,10 +79,15 @@ export const TaskTopbar: FC = ({ task, workspace }) => { + + diff --git a/site/src/pages/TasksPage/TasksPage.stories.tsx b/site/src/pages/TasksPage/TasksPage.stories.tsx index 443234c195..31302bce60 100644 --- a/site/src/pages/TasksPage/TasksPage.stories.tsx +++ b/site/src/pages/TasksPage/TasksPage.stories.tsx @@ -16,7 +16,15 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { API } from "api/api"; import { getTemplatesQueryKey } from "api/queries/templates"; import { MockUsers } from "pages/UsersPage/storybookData/users"; -import { expect, spyOn, userEvent, waitFor, within } from "storybook/test"; +import { + expect, + fireEvent, + screen, + spyOn, + userEvent, + waitFor, + within, +} from "storybook/test"; import TasksPage from "./TasksPage"; const meta: Meta = { @@ -232,6 +240,20 @@ export const NonAdmin: Story = { }, }; +export const OpenKebabMenu: Story = { + beforeEach: () => { + spyOn(API, "getTemplates").mockResolvedValue([MockTemplate]); + spyOn(API, "getTasks").mockResolvedValue(MockTasks); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const kebabButtons = await canvas.findAllByRole("button", { + name: /open task actions/i, + }); + await userEvent.click(kebabButtons[0]); + }, +}; + export const OpenDeleteDialog: Story = { beforeEach: () => { spyOn(API, "getTemplates").mockResolvedValue([MockTemplate]); @@ -239,10 +261,15 @@ export const OpenDeleteDialog: Story = { }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const deleteButtons = await canvas.findAllByRole("button", { - name: /delete task/i, + const kebabButtons = await canvas.findAllByRole("button", { + name: /open task actions/i, }); - await userEvent.click(deleteButtons[0]); + await userEvent.click(kebabButtons[0]); + + const deleteMenuItem = await screen.findByRole("menuitem", { + name: /delete/i, + }); + fireEvent.click(deleteMenuItem); }, }; diff --git a/site/src/pages/TasksPage/TasksTable.tsx b/site/src/pages/TasksPage/TasksTable.tsx index 392c0b7ed4..88361a6006 100644 --- a/site/src/pages/TasksPage/TasksTable.tsx +++ b/site/src/pages/TasksPage/TasksTable.tsx @@ -5,6 +5,13 @@ import { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/Avatar/AvatarData"; import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton"; import { Button } from "components/Button/Button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "components/DropdownMenu/DropdownMenu"; import { Skeleton } from "components/Skeleton/Skeleton"; import { Table, @@ -18,14 +25,13 @@ import { TableLoaderSkeleton, TableRowSkeleton, } from "components/TableLoader/TableLoader"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "components/Tooltip/Tooltip"; import { useClickableTableRow } from "hooks"; -import { RotateCcwIcon, TrashIcon } from "lucide-react"; +import { + EllipsisVertical, + RotateCcwIcon, + Share2Icon, + TrashIcon, +} from "lucide-react"; import { TaskDeleteDialog } from "modules/tasks/TaskDeleteDialog/TaskDeleteDialog"; import { TaskStatus } from "modules/tasks/TaskStatus/TaskStatus"; import { type FC, type ReactNode, useState } from "react"; @@ -255,24 +261,41 @@ const TaskRow: FC = ({ /> - - - - - - Delete task - - + + + + + + { + navigate( + `/@${task.owner_name}/${task.workspace_name}/settings/sharing`, + ); + }} + > + + Share workspace + + + { + e.stopPropagation(); + setIsDeleteDialogOpen(true); + }} + > + + Delete… + + + diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/ShareButton.tsx b/site/src/pages/WorkspacePage/WorkspaceActions/ShareButton.tsx index 485f208cfc..9e12caa62c 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/ShareButton.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceActions/ShareButton.tsx @@ -5,7 +5,7 @@ import { PopoverContent, PopoverTrigger, } from "components/Popover/Popover"; -import { UsersIcon } from "lucide-react"; +import { Share2Icon } from "lucide-react"; import { isGroup } from "modules/groups"; import { AddWorkspaceUserOrGroup } from "modules/workspaces/WorkspaceSharingForm/AddWorkspaceUserOrGroup"; import { useWorkspaceSharing } from "modules/workspaces/WorkspaceSharingForm/useWorkspaceSharing"; @@ -27,8 +27,8 @@ export const ShareButton: FC = ({ - - Share + + Share Workspace