Skip to content

feat(site): move history into sidebar #11413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jan 5, 2024
96 changes: 96 additions & 0 deletions site/src/components/FullPageLayout/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Interpolation, Theme, useTheme } from "@mui/material/styles";
import { ComponentProps, HTMLAttributes } from "react";
import { Link, LinkProps } from "react-router-dom";
import { TopbarIconButton } from "./Topbar";

export const Sidebar = (props: HTMLAttributes<HTMLDivElement>) => {
const theme = useTheme();
return (
<div
css={{
width: 260,
borderRight: `1px solid ${theme.palette.divider}`,
height: "100%",
overflow: "auto",
flexShrink: 0,
padding: "8px 0",
display: "flex",
flexDirection: "column",
gap: 1,
}}
{...props}
/>
);
};

export const SidebarLink = (props: LinkProps) => {
return <Link css={styles.sidebarItem} {...props} />;
};

export const SidebarItem = (props: HTMLAttributes<HTMLButtonElement>) => {
Copy link
Member

@Parkreiner Parkreiner Jan 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for future-proofing, do you think it's worth explicitly pulling the type attribute out and setting its default value to button?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I got it... do you have a code sample?

Copy link
Member

@Parkreiner Parkreiner Jan 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, just realized that the comment got garbled by a stray backtick

Now that I'm a little more awake, I think my suggestion would be overkill, but I was talking about this:

export const SidebarItem = ({
  type = "button",
  ...delegatedProps
}: HTMLAttributes<HTMLButtonElement>) => {
  return <button css={styles.sidebarItem} type={type} {...props} />;
};

return <button css={styles.sidebarItem} {...props} />;
};

export const SidebarCaption = (props: HTMLAttributes<HTMLSpanElement>) => {
return (
<span
css={{
fontSize: 10,
lineHeight: 1.2,
padding: "12px 16px",
display: "block",
textTransform: "uppercase",
fontWeight: 500,
letterSpacing: "0.1em",
}}
{...props}
/>
);
};

export const SidebarIconButton = (
props: { isActive: boolean } & ComponentProps<typeof TopbarIconButton>,
) => {
const theme = useTheme();

return (
<TopbarIconButton
css={[
{ opacity: 0.75, "&:hover": { opacity: 1 } },
props.isActive && {
opacity: 1,
position: "relative",
"&::before": {
content: '""',
position: "absolute",
left: 0,
top: 0,
bottom: 0,
width: 2,
backgroundColor: theme.palette.primary.main,
height: "100%",
},
},
]}
{...props}
/>
);
};

const styles = {
sidebarItem: (theme) => ({
fontSize: 13,
lineHeight: 1.2,
color: theme.palette.text.primary,
textDecoration: "none",
padding: "8px 16px",
display: "block",
textAlign: "left",
background: "none",
border: 0,

"&:hover": {
backgroundColor: theme.palette.action.hover,
},
}),
} satisfies Record<string, Interpolation<Theme>>;
7 changes: 3 additions & 4 deletions site/src/components/Resources/AgentRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -560,10 +560,11 @@ const styles = {
}),

agentInfo: (theme) => ({
padding: "16px 32px",
padding: "24px 32px",
display: "flex",
gap: 16,
alignItems: "center",
gap: 48,
justifyContent: "space-between",
flexWrap: "wrap",
backgroundColor: theme.palette.background.paper,

Expand All @@ -586,9 +587,7 @@ const styles = {
agentButtons: (theme) => ({
display: "flex",
gap: 8,
justifyContent: "flex-end",
flexWrap: "wrap",
flex: 1,

[theme.breakpoints.down("md")]: {
marginLeft: 0,
Expand Down
8 changes: 7 additions & 1 deletion site/src/components/Resources/Resources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { WorkspaceAgent, WorkspaceResource } from "api/typesGenerated";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { Stack } from "../Stack/Stack";
import { ResourceCard } from "./ResourceCard";
import { useTheme } from "@mui/material/styles";

const countAgents = (resource: WorkspaceResource) => {
return resource.agents ? resource.agents.length : 0;
Expand All @@ -19,6 +20,7 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
resources,
agentRow,
}) => {
const theme = useTheme();
const [shouldDisplayHideResources, setShouldDisplayHideResources] =
useState(false);
const displayResources = shouldDisplayHideResources
Expand All @@ -30,7 +32,11 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
const hasHideResources = resources.some((r) => r.hide);

return (
<Stack direction="column" spacing={0}>
<Stack
direction="column"
spacing={0}
css={{ background: theme.palette.background.default }}
>
{displayResources.map((resource) => (
<ResourceCard
key={resource.id}
Expand Down
79 changes: 79 additions & 0 deletions site/src/components/WorkspaceBuild/WorkspaceBuildData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Interpolation, Theme, useTheme } from "@emotion/react";
import Skeleton from "@mui/material/Skeleton";
import { WorkspaceBuild } from "api/typesGenerated";
import { BuildIcon } from "components/BuildIcon/BuildIcon";
import { createDayString } from "utils/createDayString";
import {
getDisplayWorkspaceBuildStatus,
getDisplayWorkspaceBuildInitiatedBy,
} from "utils/workspace";

export const WorkspaceBuildData = ({ build }: { build: WorkspaceBuild }) => {
const theme = useTheme();
const statusType = getDisplayWorkspaceBuildStatus(theme, build).type;

return (
<div css={styles.root}>
<BuildIcon
transition={build.transition}
css={{
width: 16,
height: 16,
color: theme.palette[statusType].light,
}}
/>
<div css={{ overflow: "hidden" }}>
<div
css={{
textTransform: "capitalize",
color: theme.palette.text.primary,
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
}}
>
{build.transition} by{" "}
<span css={{ fontWeight: 500 }}>
{getDisplayWorkspaceBuildInitiatedBy(build)}
</span>
</div>
<div
css={{
fontSize: 12,
color: theme.palette.text.secondary,
marginTop: 2,
}}
>
{createDayString(build.created_at)}
</div>
</div>
</div>
);
};

export const WorkspaceBuildDataSkeleton = () => {
return (
<div css={styles.root}>
<Skeleton variant="circular" width={16} height={16} />
<div>
<Skeleton variant="text" width={94} height={16} />
<Skeleton
variant="text"
width={60}
height={14}
css={{ marginTop: 2 }}
/>
</div>
</div>
);
};

const styles = {
root: {
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 12,
lineHeight: "1.4",
},
} satisfies Record<string, Interpolation<Theme>>;
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
TopbarDivider,
TopbarIconButton,
} from "components/FullPageLayout/Topbar";
import { Sidebar } from "components/FullPageLayout/Sidebar";

type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab

Expand Down Expand Up @@ -301,13 +302,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
</div>
)}

<div
css={{
width: 240,
borderRight: `1px solid ${theme.palette.divider}`,
flexShrink: 0,
}}
>
<Sidebar>
<div
css={{
height: 42,
Expand Down Expand Up @@ -409,7 +404,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
onRename={(file) => setRenameFileOpen(file)}
activePath={activePath}
/>
</div>
</Sidebar>

<div
css={{
Expand Down
99 changes: 15 additions & 84 deletions site/src/pages/WorkspaceBuildPage/WorkspaceBuildPageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@ import {
} from "components/PageHeader/FullWidthPageHeader";
import { Link } from "react-router-dom";
import { Stats, StatsItem } from "components/Stats/Stats";
import {
displayWorkspaceBuildDuration,
getDisplayWorkspaceBuildInitiatedBy,
getDisplayWorkspaceBuildStatus,
} from "utils/workspace";
import { displayWorkspaceBuildDuration } from "utils/workspace";
import { Sidebar, SidebarCaption, SidebarItem } from "./Sidebar";
import { BuildIcon } from "components/BuildIcon/BuildIcon";
import Skeleton from "@mui/material/Skeleton";
import { Alert } from "components/Alert/Alert";
import { DashboardFullPage } from "components/Dashboard/DashboardLayout";
import {
WorkspaceBuildData,
WorkspaceBuildDataSkeleton,
} from "components/WorkspaceBuild/WorkspaceBuildData";

const sortLogsByCreatedAt = (logs: ProvisionerJobLog[]) => {
return [...logs].sort(
Expand Down Expand Up @@ -112,15 +110,20 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
<SidebarCaption>Builds</SidebarCaption>
{!builds &&
Array.from({ length: 15 }, (_, i) => (
<BuildSidebarItemSkeleton key={i} />
<SidebarItem key={i}>
<WorkspaceBuildDataSkeleton />
</SidebarItem>
))}

{builds?.map((build) => (
<BuildSidebarItem
<Link
key={build.id}
build={build}
active={build.build_number === activeBuildNumber}
/>
to={`/@${build.workspace_owner_name}/${build.workspace_name}/builds/${build.build_number}`}
>
<SidebarItem active={build.build_number === activeBuildNumber}>
<WorkspaceBuildData build={build} />
</SidebarItem>
</Link>
))}
</Sidebar>

Expand Down Expand Up @@ -167,78 +170,6 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
);
};

interface BuildSidebarItemProps {
build: WorkspaceBuild;
active: boolean;
}

const BuildSidebarItem: FC<BuildSidebarItemProps> = ({ build, active }) => {
const theme = useTheme();
const statusType = getDisplayWorkspaceBuildStatus(theme, build).type;

return (
<Link
key={build.id}
to={`/@${build.workspace_owner_name}/${build.workspace_name}/builds/${build.build_number}`}
>
<SidebarItem active={active}>
<div css={{ display: "flex", alignItems: "start", gap: 8 }}>
<BuildIcon
transition={build.transition}
css={{
width: 16,
height: 16,
color: theme.palette[statusType].light,
}}
/>
<div css={{ overflow: "hidden" }}>
<div
css={{
textTransform: "capitalize",
color: theme.palette.text.primary,
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
}}
>
{build.transition} by{" "}
<strong>{getDisplayWorkspaceBuildInitiatedBy(build)}</strong>
</div>
<div
css={{
fontSize: 12,
color: theme.palette.text.secondary,
marginTop: 2,
}}
>
{displayWorkspaceBuildDuration(build)}
</div>
</div>
</div>
</SidebarItem>
</Link>
);
};

const BuildSidebarItemSkeleton: FC = () => {
return (
<SidebarItem>
<div css={{ display: "flex", alignItems: "start", gap: 8 }}>
<Skeleton variant="circular" width={16} height={16} />
<div>
<Skeleton variant="text" width={94} height={16} />
<Skeleton
variant="text"
width={60}
height={14}
css={{ marginTop: 2 }}
/>
</div>
</div>
</SidebarItem>
);
};

const styles = {
stats: (theme) => ({
padding: 0,
Expand Down
Loading