refactor: move dashboard functionality to modules/dashboard/ (#11721)

This commit is contained in:
Kayla Washburn-Love
2024-01-22 11:44:33 -07:00
committed by GitHub
parent 14f114b224
commit 69e963b1a2
68 changed files with 251 additions and 232 deletions
+3 -4
View File
@@ -170,10 +170,9 @@
"vite-plugin-turbosnap": "1.0.2"
},
"browserslist": [
"chrome 66",
"firefox 63",
"edge 79",
"safari 15.4"
"chrome 110",
"firefox 111",
"safari 16.0"
],
"resolutions": {
"optionator": "0.9.3",
+1 -1
View File
@@ -5,8 +5,8 @@ import {
BrowserRouter as Router,
Navigate,
} from "react-router-dom";
import { DashboardLayout } from "./modules/dashboard/DashboardLayout";
import { RequireAuth } from "./contexts/auth/RequireAuth";
import { DashboardLayout } from "./components/Dashboard/DashboardLayout";
import { FullScreenLoader } from "./components/Loader/FullScreenLoader";
import AuditPage from "./pages/AuditPage/AuditPage";
import { DeploySettingsLayout } from "./pages/DeploySettingsPage/DeploySettingsLayout";
@@ -1,7 +1,7 @@
import { type FC } from "react";
import type { Workspace } from "api/typesGenerated";
import { displayDormantDeletion } from "utils/dormant";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "modules/dashboard/useDashboard";
interface DormantDeletionTextProps {
workspace: Workspace;
@@ -15,7 +15,7 @@ import {
MockAppearanceConfig,
} from "testHelpers/entities";
import { WorkspaceStatusBadge } from "./WorkspaceStatusBadge";
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider";
import { DashboardContext } from "modules/dashboard/DashboardProvider";
import type { Meta, StoryObj } from "@storybook/react";
const MockedAppearance = {
@@ -29,7 +29,7 @@ const meta: Meta<typeof WorkspaceStatusBadge> = {
component: WorkspaceStatusBadge,
decorators: [
(Story) => (
<DashboardProviderContext.Provider
<DashboardContext.Provider
value={{
buildInfo: MockBuildInfo,
entitlements: MockEntitlementsWithScheduling,
@@ -38,7 +38,7 @@ const meta: Meta<typeof WorkspaceStatusBadge> = {
}}
>
<Story />
</DashboardProviderContext.Provider>
</DashboardContext.Provider>
),
],
};
+1 -1
View File
@@ -4,7 +4,7 @@ import { Outlet, Navigate, useLocation } from "react-router-dom";
import { embedRedirect } from "utils/redirect";
import { isApiError } from "api/errors";
import { ProxyProvider } from "contexts/ProxyContext";
import { DashboardProvider } from "components/Dashboard/DashboardProvider";
import { DashboardProvider } from "modules/dashboard/DashboardProvider";
import { FullScreenLoader } from "components/Loader/FullScreenLoader";
import { useAuth } from "./useAuth";
-1
View File
@@ -1,6 +1,5 @@
export * from "./useClickable";
export * from "./useClickableTableRow";
export * from "./useClipboard";
export * from "./useFeatureVisibility";
export * from "./usePagination";
export * from "./useTab";
@@ -4,10 +4,10 @@ import Button from "@mui/material/Button";
import InfoOutlined from "@mui/icons-material/InfoOutlined";
import { type FC, type HTMLAttributes, Suspense } from "react";
import { Outlet } from "react-router-dom";
import { LicenseBanner } from "modules/dashboard/LicenseBanner/LicenseBanner";
import { ServiceBanner } from "modules/dashboard/ServiceBanner/ServiceBanner";
import { usePermissions } from "contexts/auth/usePermissions";
import { LicenseBanner } from "components/Dashboard/LicenseBanner/LicenseBanner";
import { Loader } from "components/Loader/Loader";
import { ServiceBanner } from "components/Dashboard/ServiceBanner/ServiceBanner";
import { dashboardContentBottomPadding } from "theme/constants";
import { docs } from "utils/docs";
import { Navbar } from "./Navbar/Navbar";
@@ -14,7 +14,6 @@ import {
type PropsWithChildren,
createContext,
useCallback,
useContext,
useState,
} from "react";
import { appearance } from "api/queries/appearance";
@@ -27,16 +26,16 @@ interface Appearance {
setPreview: (config: AppearanceConfig) => void;
}
interface DashboardProviderValue {
export interface DashboardValue {
buildInfo: BuildInfoResponse;
entitlements: Entitlements;
experiments: Experiments;
appearance: Appearance;
}
export const DashboardProviderContext = createContext<
DashboardProviderValue | undefined
>(undefined);
export const DashboardContext = createContext<DashboardValue | undefined>(
undefined,
);
export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
const buildInfoQuery = useQuery(buildInfo());
@@ -83,7 +82,7 @@ export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
}
return (
<DashboardProviderContext.Provider
<DashboardContext.Provider
value={{
buildInfo: buildInfoQuery.data,
entitlements: entitlementsQuery.data,
@@ -96,23 +95,6 @@ export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
}}
>
{children}
</DashboardProviderContext.Provider>
</DashboardContext.Provider>
);
};
export const useDashboard = (): DashboardProviderValue => {
const context = useContext(DashboardProviderContext);
if (!context) {
throw new Error(
"useDashboard only can be used inside of DashboardProvider",
);
}
return context;
};
export const useIsWorkspaceActionsEnabled = (): boolean => {
const { entitlements } = useDashboard();
return entitlements.features["advanced_template_scheduling"].enabled;
};
@@ -1,5 +1,5 @@
import { type FC } from "react";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "modules/dashboard/useDashboard";
import { LicenseBannerView } from "./LicenseBannerView";
export const LicenseBanner: FC = () => {
@@ -3,8 +3,8 @@ import { useAuth } from "contexts/auth/useAuth";
import { useMe } from "contexts/auth/useMe";
import { usePermissions } from "contexts/auth/usePermissions";
import { useProxy } from "contexts/ProxyContext";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useFeatureVisibility } from "hooks/useFeatureVisibility";
import { useDashboard } from "modules/dashboard/useDashboard";
import { useFeatureVisibility } from "../useFeatureVisibility";
import { NavbarView } from "./NavbarView";
export const Navbar: FC = () => {
@@ -1,5 +1,5 @@
import { type FC } from "react";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "modules/dashboard/useDashboard";
import { ServiceBannerView } from "./ServiceBannerView";
export const ServiceBanner: FC = () => {
@@ -0,0 +1,14 @@
import { useContext } from "react";
import { DashboardContext, type DashboardValue } from "./DashboardProvider";
export const useDashboard = (): DashboardValue => {
const context = useContext(DashboardContext);
if (!context) {
throw new Error(
"useDashboard only can be used inside of DashboardProvider",
);
}
return context;
};
@@ -1,5 +1,5 @@
import { FeatureName } from "api/typesGenerated";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "./useDashboard";
import { selectFeatureVisibility } from "utils/entitlements";
export const useFeatureVisibility = (): Record<FeatureName, boolean> => {
@@ -1,10 +1,10 @@
import { act, renderHook, waitFor } from "@testing-library/react";
import { useUpdateCheck } from "./useUpdateCheck";
import { QueryClient, QueryClientProvider } from "react-query";
import { type FC, type PropsWithChildren } from "react";
import { QueryClient, QueryClientProvider } from "react-query";
import { rest } from "msw";
import { MockUpdateCheck } from "testHelpers/entities";
import { server } from "testHelpers/server";
import { useUpdateCheck } from "./useUpdateCheck";
const createWrapper = (): FC<PropsWithChildren> => {
const queryClient = new QueryClient();
+6 -6
View File
@@ -1,15 +1,15 @@
import { isNonInitialPage } from "components/PaginationWidget/utils";
import { useFeatureVisibility } from "hooks/useFeatureVisibility";
import { FC } from "react";
import { type FC } from "react";
import { Helmet } from "react-helmet-async";
import { useSearchParams } from "react-router-dom";
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
import { pageTitle } from "utils/page";
import { AuditPageView } from "./AuditPageView";
import { paginatedAudits } from "api/queries/audits";
import { usePaginatedQuery } from "hooks/usePaginatedQuery";
import { useUserFilterMenu } from "components/Filter/UserFilter";
import { isNonInitialPage } from "components/PaginationWidget/utils";
import { useFilter } from "components/Filter/filter";
import { useActionFilterMenu, useResourceTypeFilterMenu } from "./AuditFilter";
import { usePaginatedQuery } from "hooks/usePaginatedQuery";
import { paginatedAudits } from "api/queries/audits";
import { AuditPageView } from "./AuditPageView";
const AuditPage: FC = () => {
const { audit_log: isAuditLogVisible } = useFeatureVisibility();
@@ -10,9 +10,9 @@ import {
createTemplate,
} from "api/queries/templates";
import { useOrganizationId } from "contexts/auth/useOrganizationId";
import { useDashboard } from "modules/dashboard/useDashboard";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Loader } from "components/Loader/Loader";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { CreateTemplateForm } from "./CreateTemplateForm";
import { firstVersionFromFile, getFormPermissions, newTemplate } from "./utils";
@@ -9,9 +9,9 @@ import {
templateVersionVariables,
} from "api/queries/templates";
import { useOrganizationId } from "contexts/auth/useOrganizationId";
import { useDashboard } from "modules/dashboard/useDashboard";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Loader } from "components/Loader/Loader";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { CreateTemplateForm } from "./CreateTemplateForm";
import {
firstVersionFromExample,
@@ -8,7 +8,7 @@ import {
} from "api/queries/templates";
import { uploadFile } from "api/queries/files";
import { useOrganizationId } from "contexts/auth/useOrganizationId";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "modules/dashboard/useDashboard";
import { CreateTemplateForm } from "./CreateTemplateForm";
import { firstVersionFromFile, getFormPermissions, newTemplate } from "./utils";
@@ -1,13 +1,13 @@
import { UpdateAppearanceConfig } from "api/typesGenerated";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { FC } from "react";
import { type FC } from "react";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
import { AppearanceSettingsPageView } from "./AppearanceSettingsPageView";
import { useMutation, useQueryClient } from "react-query";
import type { UpdateAppearanceConfig } from "api/typesGenerated";
import { useDashboard } from "modules/dashboard/useDashboard";
import { pageTitle } from "utils/page";
import { updateAppearance } from "api/queries/appearance";
import { getErrorMessage } from "api/errors";
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
import { AppearanceSettingsPageView } from "./AppearanceSettingsPageView";
// ServiceBanner is unlike the other Deployment Settings pages because it
// implements a form, whereas the others are read-only. We make this
@@ -1,8 +1,8 @@
import { type FC } from "react";
import { Helmet } from "react-helmet-async";
import { useQuery } from "react-query";
import { getApps } from "api/queries/oauth2";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { FC } from "react";
import { Helmet } from "react-helmet-async";
import { useDashboard } from "modules/dashboard/useDashboard";
import { pageTitle } from "utils/page";
import OAuth2AppsSettingsPageView from "./OAuth2AppsSettingsPageView";
@@ -1,7 +1,7 @@
import { type FC } from "react";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "modules/dashboard/useDashboard";
import { useDeploySettings } from "../DeploySettingsLayout";
import { ObservabilitySettingsPageView } from "./ObservabilitySettingsPageView";
@@ -1,7 +1,7 @@
import { type FC } from "react";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "modules/dashboard/useDashboard";
import { SecuritySettingsPageView } from "./SecuritySettingsPageView";
import { useDeploySettings } from "../DeploySettingsLayout";
+2 -2
View File
@@ -5,9 +5,9 @@ import { getErrorMessage } from "api/errors";
import { groups } from "api/queries/groups";
import { useOrganizationId } from "contexts/auth/useOrganizationId";
import { usePermissions } from "contexts/auth/usePermissions";
import { useFeatureVisibility } from "hooks/useFeatureVisibility";
import { pageTitle } from "utils/page";
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
import { displayError } from "components/GlobalSnackbar/utils";
import { pageTitle } from "utils/page";
import GroupsPageView from "./GroupsPageView";
export const GroupsPage: FC = () => {
@@ -13,4 +13,6 @@ const meta: Meta = {
export default meta;
type Story = StoryObj;
export const Default: Story = {};
const Example: Story = {};
export { Example as AccessURL };
@@ -13,4 +13,6 @@ const meta: Meta = {
export default meta;
type Story = StoryObj;
export const Default: Story = {};
const Example: Story = {};
export { Example as DERP };
@@ -18,4 +18,6 @@ const meta: Meta = {
export default meta;
type Story = StoryObj;
export const Default: Story = {};
const Example: Story = {};
export { Example as DERPRegion };
@@ -13,4 +13,6 @@ const meta: Meta = {
export default meta;
type Story = StoryObj;
export const Default: Story = {};
const Example: Story = {};
export { Example as Database };
+54 -42
View File
@@ -1,24 +1,25 @@
import { useMutation, useQuery, useQueryClient } from "react-query";
import { Loader } from "components/Loader/Loader";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
import { createDayString } from "utils/createDayString";
import { DashboardFullPage } from "components/Dashboard/DashboardLayout";
import ReplayIcon from "@mui/icons-material/Replay";
import { health, refreshHealth } from "api/queries/debug";
import { useTheme } from "@mui/material/styles";
import IconButton from "@mui/material/IconButton";
import Tooltip from "@mui/material/Tooltip";
import CircularProgress from "@mui/material/CircularProgress";
import { NavLink, Outlet } from "react-router-dom";
import { css } from "@emotion/css";
import kebabCase from "lodash/fp/kebabCase";
import { Suspense } from "react";
import { HealthIcon } from "./Content";
import { HealthSeverity } from "api/typesGenerated";
import ReplayIcon from "@mui/icons-material/Replay";
import NotificationsOffOutlined from "@mui/icons-material/NotificationsOffOutlined";
import { cx } from "@emotion/css";
import { useTheme } from "@emotion/react";
import { type FC, Suspense } from "react";
import { Helmet } from "react-helmet-async";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { NavLink, Outlet } from "react-router-dom";
import kebabCase from "lodash/fp/kebabCase";
import { health, refreshHealth } from "api/queries/debug";
import type { HealthSeverity } from "api/typesGenerated";
import { type ClassName, useClassName } from "hooks/useClassName";
import { pageTitle } from "utils/page";
import { createDayString } from "utils/createDayString";
import { DashboardFullPage } from "modules/dashboard/DashboardLayout";
import { Loader } from "components/Loader/Loader";
import { HealthIcon } from "./Content";
export function HealthLayout() {
export const HealthLayout: FC = () => {
const theme = useTheme();
const queryClient = useQueryClient();
const { data: healthStatus } = useQuery({
@@ -38,6 +39,9 @@ export function HealthLayout() {
} as const;
const visibleSections = filterVisibleSections(sections);
const link = useClassName(classNames.link, []);
const activeLink = useClassName(classNames.activeLink, []);
return (
<>
<Helmet>
@@ -162,31 +166,7 @@ export function HealthLayout() {
key={key}
to={`/health/${kebabCase(key)}`}
className={({ isActive }) =>
css({
background: isActive
? theme.palette.action.hover
: "none",
pointerEvents: isActive ? "none" : "auto",
color: isActive
? theme.palette.text.primary
: theme.palette.text.secondary,
border: "none",
fontSize: 14,
width: "100%",
display: "flex",
alignItems: "center",
gap: 12,
textAlign: "left",
height: 36,
padding: "0 24px",
cursor: "pointer",
textDecoration: "none",
"&:hover": {
background: theme.palette.action.hover,
color: theme.palette.text.primary,
},
})
cx([link, isActive && activeLink])
}
>
<HealthIcon
@@ -221,7 +201,7 @@ export function HealthLayout() {
)}
</>
);
}
};
const filterVisibleSections = <T extends object>(sections: T) => {
return Object.keys(sections).reduce(
@@ -240,3 +220,35 @@ const filterVisibleSections = <T extends object>(sections: T) => {
{} as Partial<typeof sections>,
);
};
const classNames = {
link: (css, theme) =>
css({
background: "none",
pointerEvents: "auto",
color: theme.palette.text.secondary,
border: "none",
fontSize: 14,
width: "100%",
display: "flex",
alignItems: "center",
gap: 12,
textAlign: "left",
height: 36,
padding: "0 24px",
cursor: "pointer",
textDecoration: "none",
"&:hover": {
background: theme.palette.action.hover,
color: theme.palette.text.primary,
},
}),
activeLink: (css, theme) =>
css({
background: theme.palette.action.hover,
pointerEvents: "none",
color: theme.palette.text.primary,
}),
} satisfies Record<string, ClassName>;
@@ -13,4 +13,6 @@ const meta: Meta = {
export default meta;
type Story = StoryObj;
export const Default: Story = {};
const Example: Story = {};
export { Example as ProvisionerDaemons };
@@ -13,4 +13,6 @@ const meta: Meta = {
export default meta;
type Story = StoryObj;
export const Default: Story = {};
const Example: Story = {};
export { Example as Websocket };
@@ -13,4 +13,6 @@ const meta: Meta = {
export default meta;
type Story = StoryObj;
export const Default: Story = {};
const Example: Story = {};
export { Example as WorkspaceProxy };
+2 -2
View File
@@ -14,7 +14,7 @@ import {
MockHealthSettings,
} from "testHelpers/entities";
import { HEALTH_QUERY_KEY, HEALTH_QUERY_SETTINGS_KEY } from "api/queries/debug";
import { DashboardProvider } from "components/Dashboard/DashboardProvider";
import { DashboardProvider } from "modules/dashboard/DashboardProvider";
import { HealthLayout } from "./HealthLayout";
type MetaOptions = {
@@ -25,7 +25,7 @@ type MetaOptions = {
export const generateMeta = ({ element, path, params }: MetaOptions): Meta => {
return {
render: HealthLayout,
component: HealthLayout,
parameters: {
chromatic,
layout: "fullscreen",
@@ -2,12 +2,12 @@ import { type FC } from "react";
import { Helmet } from "react-helmet-async";
import { useMutation, useQueryClient } from "react-query";
import { useNavigate, useParams } from "react-router-dom";
import { pageTitle } from "utils/page";
import { updateTemplateMeta } from "api/api";
import type { UpdateTemplateMeta } from "api/typesGenerated";
import { templateByNameKey } from "api/queries/templates";
import { useOrganizationId } from "contexts/auth/useOrganizationId";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "modules/dashboard/useDashboard";
import { pageTitle } from "utils/page";
import { displaySuccess } from "components/GlobalSnackbar/utils";
import { useTemplateSettings } from "../TemplateSettingsLayout";
import { TemplateSettingsPageView } from "./TemplateSettingsPageView";
@@ -8,16 +8,14 @@ import { setGroupRole, setUserRole, templateACL } from "api/queries/templates";
import { Paywall } from "components/Paywall/Paywall";
import { Stack } from "components/Stack/Stack";
import { displaySuccess } from "components/GlobalSnackbar/utils";
import { useFeatureVisibility } from "hooks/useFeatureVisibility";
import { useOrganizationId } from "contexts/auth/useOrganizationId";
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
import { pageTitle } from "utils/page";
import { docs } from "utils/docs";
import { useTemplateSettings } from "../TemplateSettingsLayout";
import { TemplatePermissionsPageView } from "./TemplatePermissionsPageView";
export const TemplatePermissionsPage: FC<
React.PropsWithChildren<unknown>
> = () => {
export const TemplatePermissionsPage: FC = () => {
const organizationId = useOrganizationId();
const { template, permissions } = useTemplateSettings();
const { template_rbac: isTemplateRBACEnabled } = useFeatureVisibility();
@@ -2,12 +2,12 @@ import { type FC } from "react";
import { Helmet } from "react-helmet-async";
import { useMutation, useQueryClient } from "react-query";
import { useNavigate, useParams } from "react-router-dom";
import { pageTitle } from "utils/page";
import { updateTemplateMeta } from "api/api";
import type { UpdateTemplateMeta } from "api/typesGenerated";
import { templateByNameKey } from "api/queries/templates";
import { useOrganizationId } from "contexts/auth/useOrganizationId";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "modules/dashboard/useDashboard";
import { pageTitle } from "utils/page";
import { displaySuccess } from "components/GlobalSnackbar/utils";
import { useTemplateSettings } from "../TemplateSettingsLayout";
import { TemplateSchedulePageView } from "./TemplateSchedulePageView";
@@ -5,7 +5,7 @@ import { useAuth } from "contexts/auth/useAuth";
import { useMe } from "contexts/auth/useMe";
import { useOrganizationId } from "contexts/auth/useOrganizationId";
import { usePermissions } from "contexts/auth/usePermissions";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "modules/dashboard/useDashboard";
import { Stack } from "components/Stack/Stack";
import { Section } from "../Section";
import { AccountUserGroups } from "./AccountUserGroups";
+1 -1
View File
@@ -13,7 +13,7 @@ import {
SidebarNavItem,
} from "components/Sidebar/Sidebar";
import { GitIcon } from "components/Icons/GitIcon";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "modules/dashboard/useDashboard";
interface SidebarProps {
user: User;
+3 -3
View File
@@ -4,10 +4,10 @@ import GroupAdd from "@mui/icons-material/GroupAddOutlined";
import PersonAdd from "@mui/icons-material/PersonAddOutlined";
import { type FC, Suspense } from "react";
import { Link as RouterLink, Outlet, useNavigate } from "react-router-dom";
import { USERS_LINK } from "components/Dashboard/Navbar/NavbarView";
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
import { useFeatureVisibility } from "hooks/useFeatureVisibility";
import { usePermissions } from "contexts/auth/usePermissions";
import { USERS_LINK } from "modules/dashboard/Navbar/NavbarView";
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
import { Margins } from "components/Margins/Margins";
import { TabLink, Tabs } from "components/Tabs/Tabs";
import { Loader } from "components/Loader/Loader";
+3 -3
View File
@@ -2,7 +2,7 @@ import { type FC, useState } from "react";
import { Helmet } from "react-helmet-async";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { useSearchParams, useNavigate } from "react-router-dom";
import { type User } from "api/typesGenerated";
import type { User } from "api/typesGenerated";
import { roles } from "api/queries/roles";
import { groupsByUserId } from "api/queries/groups";
import { getErrorMessage } from "api/errors";
@@ -17,10 +17,10 @@ import {
authMethods,
} from "api/queries/users";
import { useOrganizationId } from "contexts/auth/useOrganizationId";
import { useMe } from "contexts/auth/useMe";
import { usePermissions } from "contexts/auth/usePermissions";
import { useMe } from "contexts/auth/useMe";
import { useDashboard } from "modules/dashboard/useDashboard";
import { useFilter } from "components/Filter/filter";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
@@ -1,7 +1,10 @@
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
import { BuildAvatar } from "components/BuildAvatar/BuildAvatar";
import { type FC } from "react";
import { ProvisionerJobLog, WorkspaceBuild } from "api/typesGenerated";
import type { ProvisionerJobLog, WorkspaceBuild } from "api/typesGenerated";
import { Link } from "react-router-dom";
import { displayWorkspaceBuildDuration } from "utils/workspace";
import { DashboardFullPage } from "modules/dashboard/DashboardLayout";
import { BuildAvatar } from "components/BuildAvatar/BuildAvatar";
import { Loader } from "components/Loader/Loader";
import { Stack } from "components/Stack/Stack";
import { WorkspaceBuildLogs } from "components/WorkspaceBuildLogs/WorkspaceBuildLogs";
@@ -10,16 +13,13 @@ import {
PageHeaderTitle,
PageHeaderSubtitle,
} from "components/PageHeader/FullWidthPageHeader";
import { Link } from "react-router-dom";
import { Stats, StatsItem } from "components/Stats/Stats";
import { displayWorkspaceBuildDuration } from "utils/workspace";
import { Sidebar, SidebarCaption, SidebarItem } from "./Sidebar";
import { Alert } from "components/Alert/Alert";
import { DashboardFullPage } from "components/Dashboard/DashboardLayout";
import {
WorkspaceBuildData,
WorkspaceBuildDataSkeleton,
} from "components/WorkspaceBuild/WorkspaceBuildData";
import { Sidebar, SidebarCaption, SidebarItem } from "./Sidebar";
const sortLogsByCreatedAt = (logs: ProvisionerJobLog[]) => {
return [...logs].sort(
@@ -1,14 +1,14 @@
import { action } from "@storybook/addon-actions";
import { Meta, StoryObj } from "@storybook/react";
import { WatchAgentMetadataContext } from "components/Resources/AgentMetadata";
import { ProvisionerJobLog } from "api/typesGenerated";
import * as Mocks from "testHelpers/entities";
import { Workspace } from "./Workspace";
import { withReactContext } from "storybook-react-context";
import EventSource from "eventsourcemock";
import { withReactContext } from "storybook-react-context";
import type { ProvisionerJobLog } from "api/typesGenerated";
import * as Mocks from "testHelpers/entities";
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider";
import { WorkspaceBuildLogsSection } from "pages/WorkspacePage/WorkspaceBuildLogsSection";
import { DashboardContext } from "modules/dashboard/DashboardProvider";
import { WatchAgentMetadataContext } from "components/Resources/AgentMetadata";
import { Workspace } from "./Workspace";
import { WorkspaceBuildLogsSection } from "./WorkspaceBuildLogsSection";
import { WorkspacePermissions } from "./permissions";
const MockedAppearance = {
@@ -38,7 +38,7 @@ const meta: Meta<typeof Workspace> = {
},
decorators: [
(Story) => (
<DashboardProviderContext.Provider
<DashboardContext.Provider
value={{
buildInfo: Mocks.MockBuildInfo,
entitlements: Mocks.MockEntitlementsWithScheduling,
@@ -66,7 +66,7 @@ const meta: Meta<typeof Workspace> = {
>
<Story />
</ProxyContext.Provider>
</DashboardProviderContext.Provider>
</DashboardContext.Provider>
),
withReactContext({
Context: WatchAgentMetadataContext,
@@ -1,20 +1,20 @@
import { workspaceResolveAutostart } from "api/queries/workspaceQuota";
import { Template, TemplateVersion, Workspace } from "api/typesGenerated";
import { FC, useEffect, useState } from "react";
import { type Interpolation, type Theme } from "@emotion/react";
import { type FC, useEffect, useState } from "react";
import { useQuery } from "react-query";
import { WorkspacePermissions } from "../permissions";
import dayjs from "dayjs";
import { useIsWorkspaceActionsEnabled } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "modules/dashboard/useDashboard";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import InfoOutlined from "@mui/icons-material/InfoOutlined";
import WarningRounded from "@mui/icons-material/WarningRounded";
import { MemoizedInlineMarkdown } from "components/Markdown/Markdown";
import type { WorkspacePermissions } from "../permissions";
import {
NotificationActionButton,
NotificationItem,
Notifications,
} from "./Notifications";
import { Interpolation, Theme } from "@emotion/react";
type WorkspaceNotificationsProps = {
workspace: Workspace;
@@ -103,8 +103,10 @@ export const WorkspaceNotifications: FC<WorkspaceNotificationsProps> = ({
}
// Dormant
const areActionsEnabled = useIsWorkspaceActionsEnabled();
if (areActionsEnabled && workspace.dormant_at) {
const { entitlements } = useDashboard();
const advancedSchedulingEnabled =
entitlements.features["advanced_template_scheduling"].enabled;
if (advancedSchedulingEnabled && workspace.dormant_at) {
const formatDate = (dateStr: string, timestamp: boolean): string => {
const date = new Date(dateStr);
return date.toLocaleDateString(undefined, {
@@ -1,18 +1,18 @@
import { type FC, useEffect } from "react";
import { useQuery, useQueryClient } from "react-query";
import { useParams } from "react-router-dom";
import { useEffectEvent } from "hooks/hookPolyfills";
import { useOrganizationId } from "contexts/auth/useOrganizationId";
import { watchWorkspace } from "api/api";
import type { Workspace } from "api/typesGenerated";
import { workspaceBuildsKey } from "api/queries/workspaceBuilds";
import { templateByName } from "api/queries/templates";
import { workspaceByOwnerAndName } from "api/queries/workspaces";
import { checkAuthorization } from "api/queries/authCheck";
import { useEffectEvent } from "hooks/hookPolyfills";
import { useOrganizationId } from "contexts/auth/useOrganizationId";
import { Navbar } from "modules/dashboard/Navbar/Navbar";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Loader } from "components/Loader/Loader";
import { Margins } from "components/Margins/Margins";
import { Navbar } from "components/Dashboard/Navbar/Navbar";
import { WorkspacePermissions, workspaceChecks } from "./permissions";
import { WorkspaceReadyPage } from "./WorkspaceReadyPage";
@@ -4,21 +4,15 @@ import { Helmet } from "react-helmet-async";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { useNavigate } from "react-router-dom";
import { MissingBuildParameters, restartWorkspace } from "api/api";
import { useFeatureVisibility } from "hooks/useFeatureVisibility";
import { pageTitle } from "utils/page";
import { getErrorMessage } from "api/errors";
import type * as TypesGen from "api/typesGenerated";
import { templateVersion, templateVersions } from "api/queries/templates";
import { deploymentConfig, deploymentSSHConfig } from "api/queries/deployment";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import {
ConfirmDialog,
ConfirmDialogProps,
} from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { Alert } from "components/Alert/Alert";
import { Stack } from "components/Stack/Stack";
import { useMe } from "contexts/auth/useMe";
import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs";
import { useDashboard } from "modules/dashboard/useDashboard";
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
import { pageTitle } from "utils/page";
import {
activate,
changeVersion,
@@ -28,9 +22,15 @@ import {
startWorkspace,
cancelBuild,
} from "api/queries/workspaces";
import { Alert } from "components/Alert/Alert";
import { Stack } from "components/Stack/Stack";
import {
ConfirmDialog,
ConfirmDialogProps,
} from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { displayError } from "components/GlobalSnackbar/utils";
import { WorkspacePermissions } from "./permissions";
import { ChangeVersionDialog } from "./ChangeVersionDialog";
import { WorkspacePermissions } from "./permissions";
import { UpdateBuildParametersDialog } from "./UpdateBuildParametersDialog";
import { Workspace } from "./Workspace";
import { WorkspaceBuildLogsSection } from "./WorkspaceBuildLogsSection";
@@ -1,6 +1,15 @@
import Tooltip from "@mui/material/Tooltip";
import Link from "@mui/material/Link";
import MonetizationOnOutlined from "@mui/icons-material/MonetizationOnOutlined";
import DeleteOutline from "@mui/icons-material/DeleteOutline";
import PersonOutline from "@mui/icons-material/PersonOutline";
import ArrowBackOutlined from "@mui/icons-material/ArrowBackOutlined";
import ScheduleOutlined from "@mui/icons-material/ScheduleOutlined";
import { useTheme } from "@emotion/react";
import { type FC } from "react";
import { useQuery } from "react-query";
import { Link as RouterLink } from "react-router-dom";
import type * as TypesGen from "api/typesGenerated";
import { WorkspaceActions } from "pages/WorkspacePage/WorkspaceActions/WorkspaceActions";
import {
Topbar,
TopbarAvatar,
@@ -9,27 +18,19 @@ import {
TopbarIcon,
TopbarIconButton,
} from "components/FullPageLayout/Topbar";
import Tooltip from "@mui/material/Tooltip";
import ArrowBackOutlined from "@mui/icons-material/ArrowBackOutlined";
import ScheduleOutlined from "@mui/icons-material/ScheduleOutlined";
import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge";
import {
WorkspaceScheduleControls,
shouldDisplayScheduleControls,
} from "./WorkspaceScheduleControls";
import { workspaceQuota } from "api/queries/workspaceQuota";
import { useQuery } from "react-query";
import MonetizationOnOutlined from "@mui/icons-material/MonetizationOnOutlined";
import { useTheme } from "@mui/material/styles";
import Link from "@mui/material/Link";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useDashboard } from "modules/dashboard/useDashboard";
import { displayDormantDeletion } from "utils/dormant";
import DeleteOutline from "@mui/icons-material/DeleteOutline";
import PersonOutline from "@mui/icons-material/PersonOutline";
import { Popover, PopoverTrigger } from "components/Popover/Popover";
import { HelpTooltipContent } from "components/HelpTooltip/HelpTooltip";
import { AvatarData } from "components/AvatarData/AvatarData";
import { ExternalAvatar } from "components/Avatar/Avatar";
import { WorkspaceActions } from "./WorkspaceActions/WorkspaceActions";
import { WorkspaceNotifications } from "./WorkspaceNotifications/WorkspaceNotifications";
import { WorkspacePermissions } from "./permissions";
@@ -64,30 +65,29 @@ export interface WorkspaceProps {
latestVersion?: TypesGen.TemplateVersion;
}
export const WorkspaceTopbar = (props: WorkspaceProps) => {
const {
handleStart,
handleStop,
handleRestart,
handleDelete,
handleUpdate,
handleCancel,
handleSettings,
handleChangeVersion,
handleDormantActivate,
workspace,
isUpdating,
isRestarting,
canUpdateWorkspace,
canChangeVersions,
canRetryDebugMode,
handleBuildRetry,
handleBuildRetryDebug,
isOwner,
template,
latestVersion,
permissions,
} = props;
export const WorkspaceTopbar: FC<WorkspaceProps> = ({
handleStart,
handleStop,
handleRestart,
handleDelete,
handleUpdate,
handleCancel,
handleSettings,
handleChangeVersion,
handleDormantActivate,
workspace,
isUpdating,
isRestarting,
canUpdateWorkspace,
canChangeVersions,
canRetryDebugMode,
handleBuildRetry,
handleBuildRetryDebug,
isOwner,
template,
latestVersion,
permissions,
}) => {
const theme = useTheme();
// Quota
@@ -2,16 +2,16 @@ import { type FC, useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
import { useQuery } from "react-query";
import { useSearchParams } from "react-router-dom";
import { templates } from "api/queries/templates";
import type { Workspace } from "api/typesGenerated";
import { templates } from "api/queries/templates";
import { useOrganizationId } from "contexts/auth/useOrganizationId";
import { usePermissions } from "contexts/auth/usePermissions";
import { useEffectEvent } from "hooks/hookPolyfills";
import { usePagination } from "hooks/usePagination";
import { useDashboard } from "modules/dashboard/useDashboard";
import { pageTitle } from "utils/page";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useFilter } from "components/Filter/filter";
import { useUserFilterMenu } from "components/Filter/UserFilter";
import { useEffectEvent } from "hooks/hookPolyfills";
import { useBatchActions } from "./batchActions";
import { useWorkspacesData, useWorkspaceUpdate } from "./data";
import { useTemplateFilterMenu, useStatusFilterMenu } from "./filter/menus";
@@ -19,7 +19,7 @@ import {
MockTemplate,
} from "testHelpers/entities";
import { WorkspacesPageView } from "./WorkspacesPageView";
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider";
import { DashboardContext } from "modules/dashboard/DashboardProvider";
import { ComponentProps } from "react";
import {
MockMenu,
@@ -141,7 +141,7 @@ const meta: Meta<typeof WorkspacesPageView> = {
},
decorators: [
(Story) => (
<DashboardProviderContext.Provider
<DashboardContext.Provider
value={{
buildInfo: MockBuildInfo,
entitlements: MockEntitlementsWithScheduling,
@@ -150,7 +150,7 @@ const meta: Meta<typeof WorkspacesPageView> = {
}}
>
<Story />
</DashboardProviderContext.Provider>
</DashboardContext.Provider>
),
],
};
@@ -1,9 +1,7 @@
import { type FC } from "react";
import { useTheme } from "@emotion/react";
import { useIsWorkspaceActionsEnabled } from "components/Dashboard/DashboardProvider";
import { type FC } from "react";
import { useDashboard } from "modules/dashboard/useDashboard";
import { Avatar, type AvatarProps } from "components/Avatar/Avatar";
import type { TemplateFilterMenu, StatusFilterMenu } from "./menus";
import type { TemplateOption, StatusOption } from "./options";
import {
Filter,
FilterMenu,
@@ -15,6 +13,8 @@ import {
} from "components/Filter/filter";
import { type UserFilterMenu, UserMenu } from "components/Filter/UserFilter";
import { docs } from "utils/docs";
import type { TemplateFilterMenu, StatusFilterMenu } from "./menus";
import type { TemplateOption, StatusOption } from "./options";
export const workspaceFilterQuery = {
me: "owner:me",
@@ -74,8 +74,10 @@ export const WorkspacesFilter: FC<WorkspaceFilterProps> = ({
error,
menus,
}) => {
const actionsEnabled = useIsWorkspaceActionsEnabled();
const presets = actionsEnabled ? PRESETS_WITH_DORMANT : PRESET_FILTERS;
const { entitlements } = useDashboard();
const presets = entitlements.features["advanced_template_scheduling"].enabled
? PRESETS_WITH_DORMANT
: PRESET_FILTERS;
return (
<Filter
+2 -2
View File
@@ -7,9 +7,9 @@ import {
import { type ReactNode, useState } from "react";
import { QueryClient } from "react-query";
import { AppProviders } from "App";
import { ThemeProvider } from "contexts/ThemeProvider";
import { DashboardLayout } from "components/Dashboard/DashboardLayout";
import { RequireAuth } from "contexts/auth/RequireAuth";
import { ThemeProvider } from "contexts/ThemeProvider";
import { DashboardLayout } from "modules/dashboard/DashboardLayout";
import { TemplateSettingsLayout } from "pages/TemplateSettingsPage/TemplateSettingsLayout";
import { WorkspaceSettingsLayout } from "pages/WorkspaceSettingsPage/WorkspaceSettingsLayout";
import {
+12 -14
View File
@@ -1,14 +1,13 @@
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider";
import { type FC } from "react";
import type { StoryContext } from "@storybook/react";
import { withDefaultFeatures } from "api/api";
import type { Entitlements } from "api/typesGenerated";
import { DashboardContext } from "modules/dashboard/DashboardProvider";
import {
MockAppearanceConfig,
MockBuildInfo,
MockEntitlements,
} from "./entities";
import { FC } from "react";
import { StoryContext } from "@storybook/react";
import * as _storybook_types from "@storybook/react";
import { Entitlements } from "api/typesGenerated";
import { withDefaultFeatures } from "api/api";
export const withDashboardProvider = (
Story: FC,
@@ -19,18 +18,17 @@ export const withDashboardProvider = (
const entitlements: Entitlements = {
...MockEntitlements,
features: withDefaultFeatures(
features.reduce(
(acc, feature) => {
acc[feature] = { enabled: true, entitlement: "entitled" };
return acc;
},
{} as Entitlements["features"],
Object.fromEntries(
features.map((feature) => [
feature,
{ enabled: true, entitlement: "entitled" },
]),
),
),
};
return (
<DashboardProviderContext.Provider
<DashboardContext.Provider
value={{
buildInfo: MockBuildInfo,
entitlements,
@@ -43,6 +41,6 @@ export const withDashboardProvider = (
}}
>
<Story />
</DashboardProviderContext.Provider>
</DashboardContext.Provider>
);
};
+2 -3
View File
@@ -84,14 +84,13 @@ export default defineConfig({
alias: {
api: path.resolve(__dirname, "./src/api"),
components: path.resolve(__dirname, "./src/components"),
hooks: path.resolve(__dirname, "./src/hooks"),
contexts: path.resolve(__dirname, "./src/contexts"),
i18n: path.resolve(__dirname, "./src/i18n"),
hooks: path.resolve(__dirname, "./src/hooks"),
modules: path.resolve(__dirname, "./src/modules"),
pages: path.resolve(__dirname, "./src/pages"),
testHelpers: path.resolve(__dirname, "./src/testHelpers"),
theme: path.resolve(__dirname, "./src/theme"),
utils: path.resolve(__dirname, "./src/utils"),
xServices: path.resolve(__dirname, "./src/xServices"),
},
},
});