feat: add impending deletion indicators to the workspace page (#7588)

* created WorkspaceDeletion directory

* remove commented code

* attempting to fix workspace stories

* fix lint

* fix the rest of the stories

* fix right stories

* PR comments

* fix lint
This commit is contained in:
Kira Pilot
2023-05-18 11:08:55 -07:00
committed by GitHub
parent 8e31ed4072
commit 0b15b1bcd1
20 changed files with 422 additions and 142 deletions
@@ -2,12 +2,12 @@ import { action } from "@storybook/addon-actions"
import { Story } from "@storybook/react"
import { WatchAgentMetadataContext } from "components/Resources/AgentMetadata"
import { ProvisionerJobLog } from "api/typesGenerated"
import * as Mocks from "../../testHelpers/entities"
import * as Mocks from "testHelpers/entities"
import { Workspace, WorkspaceErrors, WorkspaceProps } from "./Workspace"
import { withReactContext } from "storybook-react-context"
import EventSource from "eventsourcemock"
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext"
import { MockProxyLatencies } from "../../testHelpers/entities"
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider"
export default {
title: "components/Workspace",
@@ -24,21 +24,37 @@ export default {
],
}
const MockedAppearance = {
config: Mocks.MockAppearance,
preview: false,
setPreview: () => null,
save: () => null,
}
const Template: Story<WorkspaceProps> = (args) => (
<ProxyContext.Provider
<DashboardProviderContext.Provider
value={{
proxyLatencies: MockProxyLatencies,
proxy: getPreferredProxy([], undefined),
proxies: [],
isLoading: false,
isFetched: true,
setProxy: () => {
return
},
buildInfo: Mocks.MockBuildInfo,
entitlements: Mocks.MockEntitlementsWithScheduling,
experiments: Mocks.MockExperiments,
appearance: MockedAppearance,
}}
>
<Workspace {...args} />
</ProxyContext.Provider>
<ProxyContext.Provider
value={{
proxyLatencies: Mocks.MockProxyLatencies,
proxy: getPreferredProxy([], undefined),
proxies: [],
isLoading: false,
isFetched: true,
setProxy: () => {
return
},
}}
>
<Workspace {...args} />
</ProxyContext.Provider>
</DashboardProviderContext.Provider>
)
export const Running = Template.bind({})
@@ -0,0 +1,31 @@
import { Workspace } from "api/typesGenerated"
import { displayImpendingDeletion } from "./utils"
import { useDashboard } from "components/Dashboard/DashboardProvider"
import { Pill } from "components/Pill/Pill"
import ErrorIcon from "@mui/icons-material/ErrorOutline"
export const ImpendingDeletionBadge = ({
workspace,
}: {
workspace: Workspace
}): JSX.Element | null => {
const { entitlements, experiments } = useDashboard()
const allowAdvancedScheduling =
entitlements.features["advanced_template_scheduling"].enabled
// This check can be removed when https://github.com/coder/coder/milestone/19
// is merged up
const allowWorkspaceActions = experiments.includes("workspace_actions")
// return null
if (
!displayImpendingDeletion(
workspace,
allowAdvancedScheduling,
allowWorkspaceActions,
)
) {
return null
}
return <Pill icon={<ErrorIcon />} text="Impending deletion" type="error" />
}
@@ -0,0 +1,40 @@
import { Workspace } from "api/typesGenerated"
import { displayImpendingDeletion } from "./utils"
import { useDashboard } from "components/Dashboard/DashboardProvider"
import { Maybe } from "components/Conditionals/Maybe"
import { Alert } from "components/Alert/Alert"
export const ImpendingDeletionBanner = ({
workspace,
onDismiss,
displayImpendingDeletionBanner,
}: {
workspace?: Workspace
onDismiss: () => void
displayImpendingDeletionBanner: boolean
}): JSX.Element | null => {
const { entitlements, experiments } = useDashboard()
const allowAdvancedScheduling =
entitlements.features["advanced_template_scheduling"].enabled
// This check can be removed when https://github.com/coder/coder/milestone/19
// is merged up
const allowWorkspaceActions = experiments.includes("workspace_actions")
return (
<Maybe
condition={Boolean(
workspace &&
displayImpendingDeletion(
workspace,
allowAdvancedScheduling,
allowWorkspaceActions,
) &&
displayImpendingDeletionBanner,
)}
>
<Alert severity="info" onDismiss={onDismiss} dismissible>
You have workspaces that will be deleted soon.
</Alert>
</Maybe>
)
}
@@ -0,0 +1,59 @@
import { Maybe } from "components/Conditionals/Maybe"
import { StatsItem } from "components/Stats/Stats"
import Link from "@mui/material/Link"
import { Link as RouterLink } from "react-router-dom"
import styled from "@emotion/styled"
import { Workspace } from "api/typesGenerated"
import { displayImpendingDeletion } from "./utils"
import { useDashboard } from "components/Dashboard/DashboardProvider"
export const ImpendingDeletionStat = ({
workspace,
}: {
workspace: Workspace
}): JSX.Element => {
const { entitlements, experiments } = useDashboard()
const allowAdvancedScheduling =
entitlements.features["advanced_template_scheduling"].enabled
// This check can be removed when https://github.com/coder/coder/milestone/19
// is merged up
const allowWorkspaceActions = experiments.includes("workspace_actions")
return (
<Maybe
condition={displayImpendingDeletion(
workspace,
allowAdvancedScheduling,
allowWorkspaceActions,
)}
>
<StyledStatsItem
label="Deletion on"
className="containerClass"
value={
<Link
component={RouterLink}
to={`/templates/${workspace.template_name}/settings/schedule`}
title="Schedule settings"
>
{/* We check for string existence in the conditional */}
{new Date(workspace.deleting_at as string).toLocaleString()}
</Link>
}
/>
</Maybe>
)
}
const StyledStatsItem = styled(StatsItem)(() => ({
"&.containerClass": {
flexDirection: "column",
gap: 0,
padding: 0,
"& > span:first-of-type": {
fontSize: 12,
fontWeight: 500,
},
},
}))
@@ -0,0 +1,34 @@
import { Workspace } from "api/typesGenerated"
import { displayImpendingDeletion } from "./utils"
import { useDashboard } from "components/Dashboard/DashboardProvider"
import styled from "@emotion/styled"
import { Theme as MaterialUITheme } from "@mui/material/styles"
export const ImpendingDeletionText = ({
workspace,
}: {
workspace: Workspace
}): JSX.Element | null => {
const { entitlements, experiments } = useDashboard()
const allowAdvancedScheduling =
entitlements.features["advanced_template_scheduling"].enabled
// This check can be removed when https://github.com/coder/coder/milestone/19
// is merged up
const allowWorkspaceActions = experiments.includes("workspace_actions")
if (
!displayImpendingDeletion(
workspace,
allowAdvancedScheduling,
allowWorkspaceActions,
)
) {
return null
}
return <StyledSpan role="status">Impending deletion</StyledSpan>
}
const StyledSpan = styled.span<{ theme?: MaterialUITheme }>`
color: ${(props) => props.theme.palette.warning.light};
font-weight: 600;
`
@@ -0,0 +1,4 @@
export * from "./ImpendingDeletionStat"
export * from "./ImpendingDeletionBadge"
export * from "./ImpendingDeletionText"
export * from "./ImpendingDeletionBanner"
@@ -0,0 +1,56 @@
import * as TypesGen from "api/typesGenerated"
import * as Mocks from "testHelpers/entities"
import { displayImpendingDeletion } from "./utils"
describe("displayImpendingDeletion", () => {
const today = new Date()
it.each<[string, boolean, boolean, boolean]>([
[
new Date(new Date().setDate(today.getDate() + 15)).toISOString(),
true,
true,
false,
], // today + 15 days out
[
new Date(new Date().setDate(today.getDate() + 14)).toISOString(),
true,
true,
true,
], // today + 14
[
new Date(new Date().setDate(today.getDate() + 13)).toISOString(),
true,
true,
true,
], // today + 13
[
new Date(new Date().setDate(today.getDate() + 1)).toISOString(),
true,
true,
true,
], // today + 1
[new Date().toISOString(), true, true, true], // today + 0
[new Date().toISOString(), false, true, false], // Advanced Scheduling off
[new Date().toISOString(), true, false, false], // Workspace Actions off
])(
`deleting_at=%p, allowAdvancedScheduling=%p, AllowWorkspaceActions=%p, shouldDisplay=%p`,
(
deleting_at,
allowAdvancedScheduling,
allowWorkspaceActions,
shouldDisplay,
) => {
const workspace: TypesGen.Workspace = {
...Mocks.MockWorkspace,
deleting_at,
}
expect(
displayImpendingDeletion(
workspace,
allowAdvancedScheduling,
allowWorkspaceActions,
),
).toBe(shouldDisplay)
},
)
})
@@ -0,0 +1,33 @@
import { Workspace } from "api/typesGenerated"
// This const dictates how far out we alert the user that a workspace
// has an impending deletion (due to template.InactivityTTL being set)
const IMPENDING_DELETION_DISPLAY_THRESHOLD = 14 // 14 days
/**
* Returns a boolean indicating if an impending deletion indicator should be
* displayed in the UI. Impending deletions are configured by setting the
* Template.InactivityTTL
* @param {TypesGen.Workspace} workspace
* @returns {boolean}
*/
export const displayImpendingDeletion = (
workspace: Workspace,
allowAdvancedScheduling: boolean,
allowWorkspaceActions: boolean,
) => {
const today = new Date()
if (
!workspace.deleting_at ||
!allowAdvancedScheduling ||
!allowWorkspaceActions
) {
return false
}
return (
new Date(workspace.deleting_at) <=
new Date(
today.setDate(today.getDate() + IMPENDING_DELETION_DISPLAY_THRESHOLD),
)
)
}
@@ -1,17 +1,40 @@
import { Story } from "@storybook/react"
import { MockWorkspace } from "testHelpers/entities"
import {
MockWorkspace,
MockAppearance,
MockBuildInfo,
MockEntitlementsWithScheduling,
MockExperiments,
} from "testHelpers/entities"
import {
WorkspaceStats,
WorkspaceStatsProps,
} from "../WorkspaceStats/WorkspaceStats"
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider"
export default {
title: "components/WorkspaceStats",
component: WorkspaceStats,
}
const MockedAppearance = {
config: MockAppearance,
preview: false,
setPreview: () => null,
save: () => null,
}
const Template: Story<WorkspaceStatsProps> = (args) => (
<WorkspaceStats {...args} />
<DashboardProviderContext.Provider
value={{
buildInfo: MockBuildInfo,
entitlements: MockEntitlementsWithScheduling,
experiments: MockExperiments,
appearance: MockedAppearance,
}}
>
<WorkspaceStats {...args} />
</DashboardProviderContext.Provider>
)
export const Example = Template.bind({})
@@ -20,6 +20,7 @@ import Popover from "@mui/material/Popover"
import TextField from "@mui/material/TextField"
import Button from "@mui/material/Button"
import { WorkspaceStatusText } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"
import { ImpendingDeletionStat } from "components/WorkspaceDeletion"
const Language = {
workspaceDetails: "Workspace Details",
@@ -74,6 +75,7 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
label="Status"
value={<WorkspaceStatusText workspace={workspace} />}
/>
<ImpendingDeletionStat workspace={workspace} />
<StatsItem
className={styles.statsItem}
label={Language.templateLabel}
@@ -10,19 +10,40 @@ import {
MockStoppedWorkspace,
MockStoppingWorkspace,
MockWorkspace,
MockBuildInfo,
MockEntitlementsWithScheduling,
MockExperiments,
MockAppearance,
} from "testHelpers/entities"
import {
WorkspaceStatusBadge,
WorkspaceStatusBadgeProps,
} from "./WorkspaceStatusBadge"
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider"
export default {
title: "components/WorkspaceStatusBadge",
component: WorkspaceStatusBadge,
}
const MockedAppearance = {
config: MockAppearance,
preview: false,
setPreview: () => null,
save: () => null,
}
const Template: Story<WorkspaceStatusBadgeProps> = (args) => (
<WorkspaceStatusBadge {...args} />
<DashboardProviderContext.Provider
value={{
buildInfo: MockBuildInfo,
entitlements: MockEntitlementsWithScheduling,
experiments: MockExperiments,
appearance: MockedAppearance,
}}
>
<WorkspaceStatusBadge {...args} />
</DashboardProviderContext.Provider>
)
export const Running = Template.bind({})
@@ -9,8 +9,11 @@ import i18next from "i18next"
import { FC, PropsWithChildren } from "react"
import { makeStyles } from "@mui/styles"
import { combineClasses } from "utils/combineClasses"
import { displayImpendingDeletion } from "utils/workspace"
import { useDashboard } from "components/Dashboard/DashboardProvider"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import {
ImpendingDeletionBadge,
ImpendingDeletionText,
} from "components/WorkspaceDeletion"
const LoadingIcon: FC = () => {
return <CircularProgress size={10} style={{ color: "#FFF" }} />
@@ -93,41 +96,21 @@ export type WorkspaceStatusBadgeProps = {
className?: string
}
const ImpendingDeletionBadge: FC<Partial<WorkspaceStatusBadgeProps>> = ({
className,
}) => {
const { entitlements, experiments } = useDashboard()
const allowAdvancedScheduling =
entitlements.features["advanced_template_scheduling"].enabled
// This check can be removed when https://github.com/coder/coder/milestone/19
// is merged up
const allowWorkspaceActions = experiments.includes("workspace_actions")
if (!allowAdvancedScheduling || !allowWorkspaceActions) {
return null
}
return (
<Pill
className={className}
icon={<ErrorIcon />}
text="Impending deletion"
type="error"
/>
)
}
export const WorkspaceStatusBadge: FC<
PropsWithChildren<WorkspaceStatusBadgeProps>
> = ({ workspace, className }) => {
// The ImpendingDeletionBadge component itself checks to see if the
// Advanced Scheduling feature is turned on and if the
// Workspace Actions flag is turned on.
if (displayImpendingDeletion(workspace)) {
return <ImpendingDeletionBadge className={className} />
}
const { text, icon, type } = getStatus(workspace.latest_build.status)
return <Pill className={className} icon={icon} text={text} type={type} />
return (
<ChooseOne>
{/* <ImpendingDeletionBadge/> determines its own visibility */}
<Cond condition={Boolean(ImpendingDeletionBadge({ workspace }))}>
<ImpendingDeletionBadge workspace={workspace} />
</Cond>
<Cond>
<Pill className={className} icon={icon} text={text} type={type} />
</Cond>
</ChooseOne>
)
}
export const WorkspaceStatusText: FC<
@@ -135,17 +118,26 @@ export const WorkspaceStatusText: FC<
> = ({ workspace, className }) => {
const styles = useStyles()
const { text, type } = getStatus(workspace.latest_build.status)
return (
<span
role="status"
className={combineClasses([
className,
styles.root,
styles[`type-${type}`],
])}
>
{text}
</span>
<ChooseOne>
{/* <ImpendingDeletionText/> determines its own visibility */}
<Cond condition={Boolean(ImpendingDeletionText({ workspace }))}>
<ImpendingDeletionText workspace={workspace} />
</Cond>
<Cond>
<span
role="status"
className={combineClasses([
className,
styles.root,
styles[`type-${type}`],
])}
>
{text}
</span>
</Cond>
</ChooseOne>
)
}
@@ -9,6 +9,7 @@ import { useNavigate, useParams } from "react-router-dom"
import { pageTitle } from "utils/page"
import { useTemplateSettingsContext } from "../TemplateSettingsLayout"
import { TemplateSchedulePageView } from "./TemplateSchedulePageView"
import { useLocalStorage } from "hooks"
const TemplateSchedulePage: FC = () => {
const { template: templateName } = useParams() as { template: string }
@@ -20,6 +21,7 @@ const TemplateSchedulePage: FC = () => {
// This check can be removed when https://github.com/coder/coder/milestone/19
// is merged up
const allowWorkspaceActions = experiments.includes("workspace_actions")
const { clearLocal } = useLocalStorage()
const {
mutate: updateTemplate,
@@ -30,6 +32,8 @@ const TemplateSchedulePage: FC = () => {
{
onSuccess: () => {
displaySuccess("Template updated successfully")
// clear browser-stored list of workspaces impending deletion
clearLocal("dismissedWorkspaceList")
},
},
)
@@ -18,9 +18,11 @@ import {
MockCanceledWorkspace,
MockDeletingWorkspace,
MockDeletedWorkspace,
MockWorkspaceWithDeletion,
MockBuilds,
MockTemplateVersion3,
MockUser,
MockEntitlementsWithScheduling,
} from "testHelpers/entities"
import * as api from "../../api/api"
import { Workspace } from "../../api/typesGenerated"
@@ -373,6 +375,13 @@ describe("WorkspacePage", () => {
)
})
it("shows the Impending deletion status when the workspace is impending deletion", async () => {
jest
.spyOn(api, "getEntitlements")
.mockResolvedValue(MockEntitlementsWithScheduling)
await testStatus(MockWorkspaceWithDeletion, "Impending deletion")
})
it("shows the timeline build", async () => {
await renderWorkspacePage()
const table = await screen.findByTestId("builds-table")
@@ -6,17 +6,10 @@ import { workspaceFilterQuery } from "utils/filters"
import { pageTitle } from "utils/page"
import { useWorkspacesData, useWorkspaceUpdate } from "./data"
import { WorkspacesPageView } from "./WorkspacesPageView"
import { useDashboard } from "components/Dashboard/DashboardProvider"
const WorkspacesPage: FC = () => {
const filter = useFilter(workspaceFilterQuery.me)
const pagination = usePagination()
const { entitlements, experiments } = useDashboard()
const allowAdvancedScheduling =
entitlements.features["advanced_template_scheduling"].enabled
// This check can be removed when https://github.com/coder/coder/milestone/19
// is merged up
const allowWorkspaceActions = experiments.includes("workspace_actions")
const { data, error, queryKey } = useWorkspacesData({
...pagination,
@@ -42,8 +35,6 @@ const WorkspacesPage: FC = () => {
onUpdateWorkspace={(workspace) => {
updateWorkspace.mutate(workspace)
}}
allowAdvancedScheduling={allowAdvancedScheduling}
allowWorkspaceActions={allowWorkspaceActions}
/>
</>
)
@@ -6,13 +6,20 @@ import {
Workspace,
WorkspaceStatus,
WorkspaceStatuses,
} from "../../api/typesGenerated"
import { MockWorkspace } from "../../testHelpers/entities"
import { workspaceFilterQuery } from "../../utils/filters"
} from "api/typesGenerated"
import {
MockWorkspace,
MockAppearance,
MockBuildInfo,
MockEntitlementsWithScheduling,
MockExperiments,
} from "testHelpers/entities"
import { workspaceFilterQuery } from "utils/filters"
import {
WorkspacesPageView,
WorkspacesPageViewProps,
} from "./WorkspacesPageView"
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider"
const createWorkspace = (
status: WorkspaceStatus,
@@ -55,6 +62,13 @@ const allWorkspaces = [
...Object.values(additionalWorkspaces),
]
const MockedAppearance = {
config: MockAppearance,
preview: false,
setPreview: () => null,
save: () => null,
}
export default {
title: "pages/WorkspacesPageView",
component: WorkspacesPageView,
@@ -64,7 +78,16 @@ export default {
} as ComponentMeta<typeof WorkspacesPageView>
const Template: Story<WorkspacesPageViewProps> = (args) => (
<WorkspacesPageView {...args} />
<DashboardProviderContext.Provider
value={{
buildInfo: MockBuildInfo,
entitlements: MockEntitlementsWithScheduling,
experiments: MockExperiments,
appearance: MockedAppearance,
}}
>
<WorkspacesPageView {...args} />
</DashboardProviderContext.Provider>
)
export const AllStates = Template.bind({})
@@ -1,6 +1,5 @@
import Link from "@mui/material/Link"
import { Workspace } from "api/typesGenerated"
import { Alert } from "components/Alert/Alert"
import { Maybe } from "components/Conditionals/Maybe"
import { PaginationWidgetBase } from "components/PaginationWidget/PaginationWidgetBase"
import { FC } from "react"
@@ -18,6 +17,7 @@ import { WorkspacesTable } from "components/WorkspacesTable/WorkspacesTable"
import { workspaceFilterQuery } from "utils/filters"
import { useLocalStorage } from "hooks"
import difference from "lodash/difference"
import { ImpendingDeletionBanner } from "components/WorkspaceDeletion"
import { ErrorAlert } from "components/Alert/ErrorAlert"
export const Language = {
@@ -52,8 +52,6 @@ export interface WorkspacesPageViewProps {
onPageChange: (page: number) => void
onFilter: (query: string) => void
onUpdateWorkspace: (workspace: Workspace) => void
allowAdvancedScheduling: boolean
allowWorkspaceActions: boolean
}
export const WorkspacesPageView: FC<
@@ -68,8 +66,6 @@ export const WorkspacesPageView: FC<
onFilter,
onPageChange,
onUpdateWorkspace,
allowAdvancedScheduling,
allowWorkspaceActions,
}) => {
const { saveLocal, getLocal } = useLocalStorage()
@@ -98,14 +94,6 @@ export const WorkspacesPageView: FC<
return diff && diff.length > 0
}
const displayImpendingDeletionBanner =
(allowAdvancedScheduling &&
allowWorkspaceActions &&
workspaceIdsWithImpendingDeletions &&
workspaceIdsWithImpendingDeletions.length > 0 &&
isNewWorkspacesImpendingDeletion()) ??
false
return (
<Margins>
<PageHeader>
@@ -129,20 +117,16 @@ export const WorkspacesPageView: FC<
<Maybe condition={Boolean(error)}>
<ErrorAlert error={error} />
</Maybe>
<Maybe condition={displayImpendingDeletionBanner}>
<Alert
severity="info"
onDismiss={() =>
saveLocal(
"dismissedWorkspaceList",
JSON.stringify(workspaceIdsWithImpendingDeletions),
)
}
dismissible
>
You have workspaces that will be deleted soon.
</Alert>
</Maybe>
<ImpendingDeletionBanner
workspace={workspaces?.find((workspace) => workspace.deleting_at)}
displayImpendingDeletionBanner={isNewWorkspacesImpendingDeletion()}
onDismiss={() =>
saveLocal(
"dismissedWorkspaceList",
JSON.stringify(workspaceIdsWithImpendingDeletions),
)
}
/>
<SearchBarWithFilter
filter={filter}
+1 -1
View File
@@ -827,7 +827,7 @@ export const MockDeletingWorkspace: TypesGen.Workspace = {
}
export const MockWorkspaceWithDeletion = {
...MockWorkspace,
...MockStoppedWorkspace,
deleting_at: new Date().toISOString(),
}
-18
View File
@@ -6,7 +6,6 @@ import {
getDisplayVersionStatus,
getDisplayWorkspaceBuildInitiatedBy,
getDisplayWorkspaceTemplateName,
displayImpendingDeletion,
isWorkspaceOn,
} from "./workspace"
@@ -140,21 +139,4 @@ describe("util > workspace", () => {
expect(displayed).toEqual(workspace.template_display_name)
})
})
describe("displayImpendingDeletion", () => {
const today = new Date()
it.each<[string, boolean]>([
[new Date(new Date().setDate(today.getDate() + 15)).toISOString(), false], // today + 15 days out
[new Date(new Date().setDate(today.getDate() + 14)).toISOString(), true], // today + 14
[new Date(new Date().setDate(today.getDate() + 13)).toISOString(), true], // today + 13
[new Date(new Date().setDate(today.getDate() + 1)).toISOString(), true], // today + 1
[new Date().toISOString(), true], // today + 0
])(`deleting_at=%p, isWorkspaceOn=%p`, (deleting_at, shouldDisplay) => {
const workspace: TypesGen.Workspace = {
...Mocks.MockWorkspace,
deleting_at,
}
expect(displayImpendingDeletion(workspace)).toBe(shouldDisplay)
})
})
})
-24
View File
@@ -185,27 +185,3 @@ export const getDisplayWorkspaceTemplateName = (
? workspace.template_display_name
: workspace.template_name
}
// This const dictates how far out we alert the user that a workspace
// has an impending deletion (due to template.InactivityTTL being set)
const IMPENDING_DELETION_DISPLAY_THRESHOLD = 14 // 14 days
/**
* Returns a boolean indicating if an impending deletion indicator should be
* displayed in the UI. Impending deletions are configured by setting the
* Template.InactivityTTL
* @param {TypesGen.Workspace} workspace
* @returns {boolean}
*/
export const displayImpendingDeletion = (workspace: TypesGen.Workspace) => {
const today = new Date()
if (!workspace.deleting_at) {
return false
}
return (
new Date(workspace.deleting_at) <=
new Date(
today.setDate(today.getDate() + IMPENDING_DELETION_DISPLAY_THRESHOLD),
)
)
}