8000 feat: ui alert <= 30mins from deadline by greyscaled · Pull Request #1825 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat: ui alert <= 30mins from deadline #1825

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 4 commits into from
May 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
},
}
8000
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