Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: ui autostop extension#1987

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
greyscaled merged 5 commits intomainfromvapurrmaid/gh-1460/ui-autostop-extension
Jun 2, 2022
Merged
Show file tree
Hide file tree
Changes fromall 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
7 changes: 7 additions & 0 deletionssite/src/api/api.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -270,3 +270,10 @@ export const getWorkspaceBuildLogs = async (buildname: string): Promise<TypesGen
const response = await axios.get<TypesGen.ProvisionerJobLog[]>(`/api/v2/workspacebuilds/${buildname}/logs`)
return response.data
}

export const putWorkspaceExtension = async (
workspaceId: string,
extendWorkspaceRequest: TypesGen.PutExtendWorkspaceRequest,
): Promise<void> => {
await axios.put(`/api/v2/workspaces/${workspaceId}/extend`, extendWorkspaceRequest)
}
4 changes: 4 additions & 0 deletionssite/src/components/Workspace/Workspace.stories.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,10 @@ const Template: Story<WorkspaceProps> = (args) => <Workspace {...args} />

export const Started = Template.bind({})
Started.args = {
bannerProps: {
isLoading: false,
onExtend: action("extend"),
},
workspace: Mocks.MockWorkspace,
handleStart: action("start"),
handleStop: action("stop"),
Expand Down
15 changes: 14 additions & 1 deletionsite/src/components/Workspace/Workspace.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,10 @@ import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection"
import { WorkspaceStats } from "../WorkspaceStats/WorkspaceStats"

export interface WorkspaceProps {
bannerProps: {
isLoading?: boolean
onExtend: () => void
}
handleStart: () => void
handleStop: () => void
handleDelete: () => void
Expand All@@ -28,6 +32,7 @@ export interface WorkspaceProps {
* Workspace is the top-level component for viewing an individual workspace
*/
export const Workspace: FC<WorkspaceProps> = ({
bannerProps,
handleStart,
handleStop,
handleDelete,
Expand All@@ -54,6 +59,7 @@ export const Workspace: FC<WorkspaceProps> = ({
{workspace.owner_name}
</Typography>
</div>

<WorkspaceActions
workspace={workspace}
handleStart={handleStart}
Expand All@@ -70,9 +76,16 @@ export const Workspace: FC<WorkspaceProps> = ({

<Stack direction="row" spacing={3}>
<Stack direction="column" className={styles.firstColumnSpacer} spacing={3}>
<WorkspaceScheduleBanner workspace={workspace} />
<WorkspaceScheduleBanner
isLoading={bannerProps.isLoading}
onExtend={bannerProps.onExtend}
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
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import { action } from "@storybook/addon-actions"
import { Story } from "@storybook/react"
import dayjs from "dayjs"
import utc from "dayjs/plugin/utc"
Expand All@@ -15,8 +16,11 @@ const Template: Story<WorkspaceScheduleBannerProps> = (args) => <WorkspaceSchedu

export const Example = Template.bind({})
Example.args = {
isLoading: false,
onExtend: action("extend"),
workspace: {
...Mocks.MockWorkspace,

latest_build: {
...Mocks.MockWorkspaceBuild,
deadline: dayjs().utc().format(),
Expand All@@ -26,6 +30,13 @@ Example.args = {
},
transition: "start",
},
ttl: 2 * 60 * 60 * 1000 * 1_000_000, // 2 hours

ttl_ms: 2 * 60 * 60 * 1000, // 2 hours
},
}

export const Loading = Template.bind({})
Loading.args = {
...Example.args,
isLoading: true,
}
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import Button from "@material-ui/core/Button"
import Alert from "@material-ui/lab/Alert"
import AlertTitle from "@material-ui/lab/AlertTitle"
import dayjs from "dayjs"
Expand All@@ -11,10 +12,13 @@ dayjs.extend(utc)
dayjs.extend(isSameOrBefore)

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

export interface WorkspaceScheduleBannerProps {
isLoading?: boolean
onExtend: () => void
workspace: TypesGen.Workspace
}

Expand All@@ -31,12 +35,19 @@ export const shouldDisplay = (workspace: TypesGen.Workspace): boolean => {
}
}

export const WorkspaceScheduleBanner: FC<WorkspaceScheduleBannerProps> = ({ workspace }) => {
export const WorkspaceScheduleBanner: FC<WorkspaceScheduleBannerProps> = ({isLoading, onExtend,workspace }) => {
if (!shouldDisplay(workspace)) {
return null
} else {
return (
<Alert severity="warning">
<Alert
action={
<Button color="inherit" disabled={isLoading} onClick={onExtend} size="small">
{Language.bannerAction}
</Button>
}
severity="warning"
>
<AlertTitle>{Language.bannerTitle}</AlertTitle>
</Alert>
)
Expand Down
9 changes: 9 additions & 0 deletionssite/src/pages/WorkspacePage/WorkspacePage.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ import { Stack } from "../../components/Stack/Stack"
import { Workspace } from "../../components/Workspace/Workspace"
import { firstOrItem } from "../../util/array"
import { workspaceMachine } from "../../xServices/workspace/workspaceXService"
import { workspaceScheduleBannerMachine } from "../../xServices/workspaceSchedule/workspaceScheduleBannerXService"

export const WorkspacePage: React.FC = () => {
const { workspace: workspaceQueryParam } = useParams()
Expand All@@ -18,6 +19,8 @@ export const WorkspacePage: React.FC = () => {
const [workspaceState, workspaceSend] = useMachine(workspaceMachine)
const { workspace, resources, getWorkspaceError, getResourcesError, builds } = workspaceState.context

const [bannerState, bannerSend] = useMachine(workspaceScheduleBannerMachine)

/**
* Get workspace, template, and organization on mount and whenever workspaceId changes.
* workspaceSend should not change.
Expand All@@ -36,6 +39,12 @@ export const WorkspacePage: React.FC = () => {
<Stack spacing={4}>
<>
<Workspace
bannerProps={{
isLoading: bannerState.hasTag("loading"),
onExtend: () => {
bannerSend({ type: "EXTEND_DEADLINE_DEFAULT", workspaceId: workspace.id })
},
}}
workspace={workspace}
handleStart={() => workspaceSend("START")}
handleStop={() => workspaceSend("STOP")}
Expand Down
7 changes: 5 additions & 2 deletionssite/src/testHelpers/handlers.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,6 +109,11 @@ export const handlers = [
rest.put("/api/v2/workspaces/:workspaceId/ttl",async(req,res,ctx)=>{
returnres(ctx.status(200))
}),
rest.put("/api/v2/workspaces/:workspaceId/extend",async(req,res,ctx)=>{
returnres(ctx.status(200))
}),

// workspace builds
rest.post("/api/v2/workspaces/:workspaceId/builds",async(req,res,ctx)=>{
const{ transition}=req.bodyasCreateWorkspaceBuildRequest
consttransitionToBuild={
Expand All@@ -122,8 +127,6 @@ export const handlers = [
rest.get("/api/v2/workspaces/:workspaceId/builds",async(req,res,ctx)=>{
returnres(ctx.status(200),ctx.json(M.MockBuilds))
}),

// workspace builds
rest.get("/api/v2/workspacebuilds/:workspaceBuildId",(req,res,ctx)=>{
returnres(ctx.status(200),ctx.json(M.MockWorkspaceBuild))
}),
Expand Down
25 changes: 24 additions & 1 deletionsite/src/util/workspace.test.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import dayjs from "dayjs"
import * as TypesGen from "../api/typesGenerated"
import * as Mocks from "../testHelpers/entities"
import { isWorkspaceOn } from "./workspace"
import {defaultWorkspaceExtension,isWorkspaceOn } from "./workspace"

describe("util > workspace", () => {
describe("isWorkspaceOn", () => {
Expand DownExpand Up@@ -40,4 +41,26 @@ describe("util > workspace", () => {
expect(isWorkspaceOn(workspace)).toBe(isOn)
})
})

describe("defaultWorkspaceExtension", () => {
it.each<[string, TypesGen.PutExtendWorkspaceRequest]>([
[
"2022-06-02T14:56:34Z",
{
deadline: "2022-06-02T16:26:34Z",
},
],

// This case is the same as above, but in a different timezone to prove
// that UTC conversion for deadline works as expected
[
"2022-06-02T10:56:20-04:00",
{
deadline: "2022-06-02T16:26:20Z",
},
],
])(`defaultWorkspaceExtension(%p) returns %p`, (startTime, request) => {
expect(defaultWorkspaceExtension(dayjs(startTime))).toEqual(request)
})
})
})
29 changes: 22 additions & 7 deletionssite/src/util/workspace.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
import { Theme } from "@material-ui/core/styles"
import dayjs from "dayjs"
import utc from "dayjs/plugin/utc"
import { WorkspaceBuildTransition } from "../api/types"
import { Workspace, WorkspaceAgent, WorkspaceBuild } from "../api/typesGenerated"
import * as TypesGen from "../api/typesGenerated"

dayjs.extend(utc)

export type WorkspaceStatus =
| "queued"
Expand DownExpand Up@@ -29,7 +32,7 @@ const succeededToStatus: Record<WorkspaceBuildTransition, WorkspaceStatus> = {
}

// Converts a workspaces status to a human-readable form.
export const getWorkspaceStatus = (workspaceBuild?: WorkspaceBuild): WorkspaceStatus => {
export const getWorkspaceStatus = (workspaceBuild?:TypesGen.WorkspaceBuild): WorkspaceStatus => {
const transition = workspaceBuild?.transition as WorkspaceBuildTransition
const jobStatus = workspaceBuild?.job.status
switch (jobStatus) {
Expand DownExpand Up@@ -66,7 +69,7 @@ export const DisplayStatusLanguage = {

export const getDisplayStatus = (
theme: Theme,
build: WorkspaceBuild,
build:TypesGen.WorkspaceBuild,
): {
color: string
status: string
Expand DownExpand Up@@ -132,7 +135,7 @@ export const getDisplayStatus = (
throw new Error("unknown status " + status)
}

export const getWorkspaceBuildDurationInSeconds = (build: WorkspaceBuild): number | undefined => {
export const getWorkspaceBuildDurationInSeconds = (build:TypesGen.WorkspaceBuild): number | undefined => {
const isCompleted = build.job.started_at && build.job.completed_at

if (!isCompleted) {
Expand All@@ -144,7 +147,10 @@ export const getWorkspaceBuildDurationInSeconds = (build: WorkspaceBuild): numbe
return completedAt.diff(startedAt, "seconds")
}

export const displayWorkspaceBuildDuration = (build: WorkspaceBuild, inProgressLabel = "In progress"): string => {
export const displayWorkspaceBuildDuration = (
build: TypesGen.WorkspaceBuild,
inProgressLabel = "In progress",
): string => {
const duration = getWorkspaceBuildDurationInSeconds(build)
return duration ? `${duration} seconds` : inProgressLabel
}
Expand All@@ -157,7 +163,7 @@ export const DisplayAgentStatusLanguage = {

export const getDisplayAgentStatus = (
theme: Theme,
agent: WorkspaceAgent,
agent:TypesGen.WorkspaceAgent,
): {
color: string
status: string
Expand DownExpand Up@@ -186,8 +192,17 @@ export const getDisplayAgentStatus = (
}
}

export const isWorkspaceOn = (workspace: Workspace): boolean => {
export const isWorkspaceOn = (workspace:TypesGen.Workspace): boolean => {
const transition = workspace.latest_build.transition
const status = workspace.latest_build.job.status
return transition === "start" && status === "succeeded"
}

export const defaultWorkspaceExtension = (__startDate?: dayjs.Dayjs): TypesGen.PutExtendWorkspaceRequest => {
const now = __startDate ? dayjs(__startDate) : dayjs()
const NinetyMinutesFromNow = now.add(90, "minutes").utc()

return {
deadline: NinetyMinutesFromNow.format(),
}
}
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
/**
* @fileoverview workspaceScheduleBanner is an xstate machine backing a form,
* presented as an Alert/banner, for reactively extending a workspace schedule.
*/
import { createMachine } from "xstate"
import * as API from "../../api/api"
import { displayError, displaySuccess } from "../../components/GlobalSnackbar/utils"
import { defaultWorkspaceExtension } from "../../util/workspace"

export const Language = {
errorExtension: "Failed to extend workspace deadline.",
successExtension: "Successfully extended workspace deadline.",
}

export type WorkspaceScheduleBannerEvent = { type: "EXTEND_DEADLINE_DEFAULT"; workspaceId: string }

export const workspaceScheduleBannerMachine = createMachine(
{
tsTypes: {} as import("./workspaceScheduleBannerXService.typegen").Typegen0,
schema: {
events: {} as WorkspaceScheduleBannerEvent,
},
id: "workspaceScheduleBannerState",
initial: "idle",
states: {
idle: {
on: {
EXTEND_DEADLINE_DEFAULT: "extendingDeadline",
},
},
extendingDeadline: {
invoke: {
src: "extendDeadlineDefault",
id: "extendDeadlineDefault",
onDone: {
target: "idle",
actions: "displaySuccessMessage",
},
onError: {
target: "idle",
actions: "displayFailureMessage",
},
},
tags: "loading",
},
},
},
{
actions: {
displayFailureMessage: () => {
displayError(Language.errorExtension)
},
displaySuccessMessage: () => {
displaySuccess(Language.successExtension)
},
},

services: {
extendDeadlineDefault: async (_, event) => {
await API.putWorkspaceExtension(event.workspaceId, defaultWorkspaceExtension())
},
},
},
)

[8]ページ先頭

©2009-2025 Movatter.jp