Skip to content

Commit

Permalink
feat: ui alert <= 30mins from deadline (#1825)
Browse files Browse the repository at this point in the history
Summary:

When a workspace build is <= 30 minutes from auto-scheduled shutdown,
then an alert banner is displayed on the workspace page.
  • Loading branch information
greyscaled committed May 27, 2022
1 parent ff542af commit 8d7499f
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 1 deletion.
5 changes: 5 additions & 0 deletions site/src/components/Workspace/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Resources } from "../Resources/Resources"
import { Stack } from "../Stack/Stack"
import { WorkspaceActions } from "../WorkspaceActions/WorkspaceActions"
import { WorkspaceSchedule } from "../WorkspaceSchedule/WorkspaceSchedule"
import { WorkspaceScheduleBanner } from "../WorkspaceScheduleBanner/WorkspaceScheduleBanner"
import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection"
import { WorkspaceStats } from "../WorkspaceStats/WorkspaceStats"

Expand Down Expand Up @@ -63,8 +64,12 @@ export const Workspace: React.FC<WorkspaceProps> = ({

<Stack direction="row" spacing={3} className={styles.layout}>
<Stack spacing={3} className={styles.main}>
<WorkspaceScheduleBanner workspace={workspace} />

<WorkspaceStats workspace={workspace} />

<Resources resources={resources} getResourcesError={getResourcesError} workspace={workspace} />

<WorkspaceSection title="Timeline" contentsProps={{ className: styles.timelineContents }}>
<BuildsTable builds={builds} className={styles.timelineTable} />
</WorkspaceSection>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Story } from "@storybook/react"
import dayjs from "dayjs"
import utc from "dayjs/plugin/utc"
import React from "react"
import * as Mocks from "../../testHelpers/entities"
import { WorkspaceScheduleBanner, WorkspaceScheduleBannerProps } from "./WorkspaceScheduleBanner"

dayjs.extend(utc)

export default {
title: "components/WorkspaceScheduleBanner",
component: WorkspaceScheduleBanner,
}

const Template: Story<WorkspaceScheduleBannerProps> = (args) => <WorkspaceScheduleBanner {...args} />

export const Example = Template.bind({})
Example.args = {
workspace: {
...Mocks.MockWorkspace,
latest_build: {
...Mocks.MockWorkspaceBuild,
deadline: dayjs().utc().format(),
job: {
...Mocks.MockProvisionerJob,
status: "succeeded",
},
transition: "start",
},
ttl: 2 * 60 * 60 * 1000 * 1_000_000, // 2 hours
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import dayjs from "dayjs"
import utc from "dayjs/plugin/utc"
import * as TypesGen from "../../api/typesGenerated"
import * as Mocks from "../../testHelpers/entities"
import { shouldDisplay } from "./WorkspaceScheduleBanner"

dayjs.extend(utc)

describe("WorkspaceScheduleBanner", () => {
describe("shouldDisplay", () => {
// Manual TTL case
it("should not display if the build does not have a deadline", () => {
// Given: a workspace with deadline of '"0001-01-01T00:00:00Z"'
const workspace: TypesGen.Workspace = {
...Mocks.MockWorkspace,
latest_build: {
...Mocks.MockWorkspaceBuild,
deadline: "0001-01-01T00:00:00Z",
transition: "start",
},
}

// Then: shouldDisplay is false
expect(shouldDisplay(workspace)).toBeFalsy()
})

// Transition Checks
it("should not display if the latest build is not transition=start", () => {
// Given: a workspace with latest build as "stop"
const workspace: TypesGen.Workspace = {
...Mocks.MockWorkspace,
latest_build: {
...Mocks.MockWorkspaceBuild,
transition: "stop",
},
}

// Then: shouldDisplay is false
expect(shouldDisplay(workspace)).toBeFalsy()
})

// Provisioner Job Checks
it("should not display if the latest build is canceling", () => {
// Given: a workspace with latest build as "canceling"
const workspace: TypesGen.Workspace = {
...Mocks.MockWorkspace,
latest_build: {
...Mocks.MockWorkspaceBuild,
job: Mocks.MockCancelingProvisionerJob,
transition: "start",
},
}

// Then: shouldDisplay is false
expect(shouldDisplay(workspace)).toBeFalsy()
})
it("should not display if the latest build is canceled", () => {
// Given: a workspace with latest build as "canceled"
const workspace: TypesGen.Workspace = {
...Mocks.MockWorkspace,
latest_build: {
...Mocks.MockWorkspaceBuild,
job: Mocks.MockCanceledProvisionerJob,
transition: "start",
},
}

// Then: shouldDisplay is false
expect(shouldDisplay(workspace)).toBeFalsy()
})
it("should not display if the latest build failed", () => {
// Given: a workspace with latest build as "failed"
const workspace: TypesGen.Workspace = {
...Mocks.MockWorkspace,
latest_build: {
...Mocks.MockWorkspaceBuild,
job: Mocks.MockFailedProvisionerJob,
transition: "start",
},
}

// Then: shouldDisplay is false
expect(shouldDisplay(workspace)).toBeFalsy()
})

// Deadline Checks
it("should display if deadline is within 30 minutes", () => {
// Given: a workspace with latest build as start and deadline in ~30 mins
const workspace: TypesGen.Workspace = {
...Mocks.MockWorkspace,
latest_build: {
...Mocks.MockWorkspaceBuild,
deadline: dayjs().add(27, "minutes").utc().format(),
job: Mocks.MockRunningProvisionerJob,
transition: "start",
},
}

// Then: shouldDisplay is true
expect(shouldDisplay(workspace)).toBeTruthy()
})
it("should not display if deadline is 45 minutes", () => {
// Given: a workspace with latest build as start and deadline in 45 mins
const workspace: TypesGen.Workspace = {
...Mocks.MockWorkspace,
latest_build: {
...Mocks.MockWorkspaceBuild,
deadline: dayjs().add(45, "minutes").utc().format(),
transition: "start",
},
}

// Then: shouldDisplay is false
expect(shouldDisplay(workspace)).toBeFalsy()
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Alert from "@material-ui/lab/Alert"
import AlertTitle from "@material-ui/lab/AlertTitle"
import dayjs from "dayjs"
import isSameOrBefore from "dayjs/plugin/isSameOrBefore"
import utc from "dayjs/plugin/utc"
import React from "react"
import * as TypesGen from "../../api/typesGenerated"

dayjs.extend(utc)
dayjs.extend(isSameOrBefore)

export const Language = {
bannerTitle: "Your workspace is scheduled to automatically shut down soon.",
}

export interface WorkspaceScheduleBannerProps {
workspace: TypesGen.Workspace
}

export const shouldDisplay = (workspace: TypesGen.Workspace): boolean => {
const transition = workspace.latest_build.transition
const status = workspace.latest_build.job.status

if (transition !== "start") {
return false
} else if (status === "canceled" || status === "canceling" || status === "failed") {
return false
} else {
// a mannual shutdown has a deadline of '"0001-01-01T00:00:00Z"'
// SEE: #1834
const deadline = dayjs(workspace.latest_build.deadline).utc()
const hasDeadline = deadline.year() > 1
const thirtyMinutesFromNow = dayjs().add(30, "minutes").utc()
return hasDeadline && deadline.isSameOrBefore(thirtyMinutesFromNow)
}
}

export const WorkspaceScheduleBanner: React.FC<WorkspaceScheduleBannerProps> = ({ workspace }) => {
if (!shouldDisplay(workspace)) {
return null
} else {
return (
<Alert severity="warning">
<AlertTitle>{Language.bannerTitle}</AlertTitle>
</Alert>
)
}
}
3 changes: 2 additions & 1 deletion site/src/xServices/workspace/workspaceXService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,8 @@ export const workspaceMachine = createMachine(
const oldBuilds = context.builds

if (!oldBuilds) {
throw new Error("Builds not loaded")
// This state is theoretically impossible, but helps TS
throw new Error("workspaceXService: failed to load workspace builds")
}

return [...oldBuilds, ...event.data]
Expand Down

0 comments on commit 8d7499f

Please sign in to comment.