chore: revert "chore: remove workspace_actions experiment (#10030)" (#10363)

This commit is contained in:
Jon Ayers 2023-10-20 13:21:53 -05:00 committed by GitHub
parent 57c9d88703
commit 1372bf82f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 124 additions and 36 deletions

View File

@ -50,6 +50,18 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
isTemplateSchedulingOptionsSet := failureTTL != 0 || inactivityTTL != 0 || maxTTL != 0
if isTemplateSchedulingOptionsSet || requireActiveVersion {
if failureTTL != 0 || inactivityTTL != 0 {
// This call can be removed when workspace_actions is no longer experimental
experiments, exErr := client.Experiments(inv.Context())
if exErr != nil {
return xerrors.Errorf("get experiments: %w", exErr)
}
if !experiments.Enabled(codersdk.ExperimentWorkspaceActions) {
return xerrors.Errorf("--failure-ttl and --inactivity-ttl are experimental features. Use the workspace_actions CODER_EXPERIMENTS flag to set these configuration values.")
}
}
entitlements, err := client.Entitlements(inv.Context())
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusNotFound {
return xerrors.Errorf("your deployment appears to be an AGPL deployment, so you cannot set enterprise-only flags")
@ -59,7 +71,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
if isTemplateSchedulingOptionsSet {
if !entitlements.Features[codersdk.FeatureAdvancedTemplateScheduling].Enabled {
return xerrors.Errorf("your license is not entitled to use advanced template scheduling, so you cannot set --failure-ttl or --inactivityTTL")
return xerrors.Errorf("your license is not entitled to use advanced template scheduling, so you cannot set --failure-ttl, --inactivity-ttl, or --max-ttl")
}
}

View File

@ -43,6 +43,18 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
),
Short: "Edit the metadata of a template by name.",
Handler: func(inv *clibase.Invocation) error {
// This clause can be removed when workspace_actions is no longer experimental
if failureTTL != 0 || inactivityTTL != 0 {
experiments, exErr := client.Experiments(inv.Context())
if exErr != nil {
return xerrors.Errorf("get experiments: %w", exErr)
}
if !experiments.Enabled(codersdk.ExperimentWorkspaceActions) {
return xerrors.Errorf("--failure-ttl and --inactivity-ttl are experimental features. Use the workspace_actions CODER_EXPERIMENTS flag to set these configuration values.")
}
}
unsetAutostopRequirementDaysOfWeek := len(autostopRequirementDaysOfWeek) == 1 && autostopRequirementDaysOfWeek[0] == "none"
requiresScheduling := (len(autostopRequirementDaysOfWeek) > 0 && !unsetAutostopRequirementDaysOfWeek) ||
autostopRequirementWeeks > 0 ||

2
coderd/apidoc/docs.go generated
View File

@ -8537,6 +8537,7 @@ const docTemplate = `{
"type": "string",
"enum": [
"moons",
"workspace_actions",
"tailnet_pg_coordinator",
"single_tailnet",
"template_autostop_requirement",
@ -8546,6 +8547,7 @@ const docTemplate = `{
],
"x-enum-varnames": [
"ExperimentMoons",
"ExperimentWorkspaceActions",
"ExperimentTailnetPGCoordinator",
"ExperimentSingleTailnet",
"ExperimentTemplateAutostopRequirement",

View File

@ -7649,6 +7649,7 @@
"type": "string",
"enum": [
"moons",
"workspace_actions",
"tailnet_pg_coordinator",
"single_tailnet",
"template_autostop_requirement",
@ -7658,6 +7659,7 @@
],
"x-enum-varnames": [
"ExperimentMoons",
"ExperimentWorkspaceActions",
"ExperimentTailnetPGCoordinator",
"ExperimentSingleTailnet",
"ExperimentTemplateAutostopRequirement",

View File

@ -1973,6 +1973,9 @@ const (
// feature is not yet complete in functionality.
ExperimentMoons Experiment = "moons"
// https://github.com/coder/coder/milestone/19
ExperimentWorkspaceActions Experiment = "workspace_actions"
// ExperimentTailnetPGCoordinator enables the PGCoord in favor of the pubsub-
// only Coordinator
ExperimentTailnetPGCoordinator Experiment = "tailnet_pg_coordinator"

1
docs/api/schemas.md generated
View File

@ -2845,6 +2845,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| Value |
| ------------------------------- |
| `moons` |
| `workspace_actions` |
| `tailnet_pg_coordinator` |
| `single_tailnet` |
| `template_autostop_requirement` |

View File

@ -1708,7 +1708,8 @@ export type Experiment =
| "single_tailnet"
| "tailnet_pg_coordinator"
| "template_autostop_requirement"
| "template_update_policies";
| "template_update_policies"
| "workspace_actions";
export const Experiments: Experiment[] = [
"dashboard_theme",
"deployment_health_page",
@ -1717,6 +1718,7 @@ export const Experiments: Experiment[] = [
"tailnet_pg_coordinator",
"template_autostop_requirement",
"template_update_policies",
"workspace_actions",
];
// From codersdk/deployment.go

View File

@ -112,3 +112,13 @@ export const useDashboard = (): DashboardProviderValue => {
return context;
};
export const useIsWorkspaceActionsEnabled = (): boolean => {
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 allowWorkspaceActions && allowAdvancedScheduling;
};

View File

@ -14,11 +14,20 @@ interface DormantDeletionStatProps {
export const DormantDeletionStat: FC<DormantDeletionStatProps> = ({
workspace,
}) => {
const { entitlements } = useDashboard();
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 (!displayDormantDeletion(workspace, allowAdvancedScheduling)) {
if (
!displayDormantDeletion(
workspace,
allowAdvancedScheduling,
allowWorkspaceActions,
)
) {
return null;
}

View File

@ -9,11 +9,20 @@ export const DormantDeletionText = ({
}: {
workspace: Workspace;
}): JSX.Element | null => {
const { entitlements } = useDashboard();
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 (!displayDormantDeletion(workspace, allowAdvancedScheduling)) {
if (
!displayDormantDeletion(
workspace,
allowAdvancedScheduling,
allowWorkspaceActions,
)
) {
return null;
}
return <StyledSpan role="status">Impending deletion</StyledSpan>;

View File

@ -1,5 +1,5 @@
import { Workspace } from "api/typesGenerated";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useIsWorkspaceActionsEnabled } from "components/Dashboard/DashboardProvider";
import { Alert } from "components/Alert/Alert";
import { formatDistanceToNow } from "date-fns";
import Link from "@mui/material/Link";
@ -21,9 +21,7 @@ export const DormantWorkspaceBanner = ({
shouldRedisplayBanner: boolean;
count?: Count;
}): JSX.Element | null => {
const { entitlements } = useDashboard();
const schedulingEnabled =
entitlements.features["advanced_template_scheduling"].enabled;
const experimentEnabled = useIsWorkspaceActionsEnabled();
if (!workspaces) {
return null;
@ -39,7 +37,7 @@ export const DormantWorkspaceBanner = ({
if (
// Only show this if the experiment is included.
!schedulingEnabled ||
!experimentEnabled ||
!hasDormantWorkspaces ||
// Banners should be redisplayed after dismissal when additional workspaces are newly scheduled for deletion
!shouldRedisplayBanner

View File

@ -4,39 +4,53 @@ import { displayDormantDeletion } from "./utils";
describe("displayDormantDeletion", () => {
const today = new Date();
it.each<[string, boolean, boolean]>([
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], // today + 0
[new Date().toISOString(), false, false], // Advanced Scheduling off
[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, shouldDisplay=%p`,
(deleting_at, allowAdvancedScheduling, shouldDisplay) => {
`deleting_at=%p, allowAdvancedScheduling=%p, AllowWorkspaceActions=%p, shouldDisplay=%p`,
(
deleting_at,
allowAdvancedScheduling,
allowWorkspaceActions,
shouldDisplay,
) => {
const workspace: TypesGen.Workspace = {
...Mocks.MockWorkspace,
deleting_at,
};
expect(displayDormantDeletion(workspace, allowAdvancedScheduling)).toBe(
shouldDisplay,
);
expect(
displayDormantDeletion(
workspace,
allowAdvancedScheduling,
allowWorkspaceActions,
),
).toBe(shouldDisplay);
},
);
});

View File

@ -14,9 +14,14 @@ const IMPENDING_DELETION_DISPLAY_THRESHOLD = 14; // 14 days
export const displayDormantDeletion = (
workspace: Workspace,
allowAdvancedScheduling: boolean,
allowWorkspaceActions: boolean,
) => {
const today = new Date();
if (!workspace.deleting_at || !allowAdvancedScheduling) {
if (
!workspace.deleting_at ||
!allowAdvancedScheduling ||
!allowWorkspaceActions
) {
return false;
}
return (

View File

@ -54,6 +54,7 @@ export interface TemplateScheduleForm {
isSubmitting: boolean;
error?: unknown;
allowAdvancedScheduling: boolean;
allowWorkspaceActions: boolean;
allowAutostopRequirement: boolean;
// Helpful to show field errors on Storybook
initialTouched?: FormikTouched<UpdateTemplateMeta>;
@ -65,6 +66,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
onCancel,
error,
allowAdvancedScheduling,
allowWorkspaceActions,
allowAutostopRequirement,
isSubmitting,
initialTouched,
@ -491,7 +493,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
</Stack>
</Stack>
</FormSection>
{allowAdvancedScheduling && (
{allowAdvancedScheduling && allowWorkspaceActions && (
<>
<FormSection
title="Failure Cleanup"

View File

@ -133,6 +133,9 @@ describe("TemplateSchedulePage", () => {
jest
.spyOn(API, "getEntitlements")
.mockResolvedValue(MockEntitlementsWithScheduling);
// remove when https://github.com/coder/coder/milestone/19 is completed.
jest.spyOn(API, "getExperiments").mockResolvedValue(["workspace_actions"]);
});
it("Calls the API when user fills in and submits a form", async () => {

View File

@ -18,11 +18,12 @@ const TemplateSchedulePage: FC = () => {
const queryClient = useQueryClient();
const orgId = useOrganizationId();
const { template } = useTemplateSettings();
const { entitlements } = useDashboard();
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 allowAutostopRequirement =
entitlements.features["template_autostop_requirement"].enabled;
const { clearLocal } = useLocalStorage();
@ -53,6 +54,7 @@ const TemplateSchedulePage: FC = () => {
</Helmet>
<TemplateSchedulePageView
allowAdvancedScheduling={allowAdvancedScheduling}
allowWorkspaceActions={allowWorkspaceActions}
allowAutostopRequirement={allowAutostopRequirement}
isSubmitting={isSubmitting}
template={template}

View File

@ -31,6 +31,7 @@ type Story = StoryObj<typeof TemplateSchedulePageView>;
const defaultArgs = {
allowAdvancedScheduling: true,
allowWorkspaceActions: true,
template: MockTemplate,
onSubmit: action("onSubmit"),
onCancel: action("cancel"),

View File

@ -13,6 +13,7 @@ export interface TemplateSchedulePageViewProps {
typeof TemplateScheduleForm
>["initialTouched"];
allowAdvancedScheduling: boolean;
allowWorkspaceActions: boolean;
allowAutostopRequirement: boolean;
}
@ -22,6 +23,7 @@ export const TemplateSchedulePageView: FC<TemplateSchedulePageViewProps> = ({
onSubmit,
isSubmitting,
allowAdvancedScheduling,
allowWorkspaceActions,
allowAutostopRequirement,
submitError,
initialTouched,
@ -34,6 +36,7 @@ export const TemplateSchedulePageView: FC<TemplateSchedulePageViewProps> = ({
<TemplateScheduleForm
allowAdvancedScheduling={allowAdvancedScheduling}
allowWorkspaceActions={allowWorkspaceActions}
allowAutostopRequirement={allowAutostopRequirement}
initialTouched={initialTouched}
isSubmitting={isSubmitting}

View File

@ -1,6 +1,9 @@
import { usePagination } from "hooks/usePagination";
import { Workspace } from "api/typesGenerated";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import {
useDashboard,
useIsWorkspaceActionsEnabled,
} from "components/Dashboard/DashboardProvider";
import { type FC, useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
import { pageTitle } from "utils/page";
@ -58,17 +61,14 @@ const WorkspacesPage: FC = () => {
query: filterProps.filter.query,
});
const { entitlements } = useDashboard();
const schedulingEnabled =
entitlements.features["advanced_template_scheduling"].enabled;
const experimentEnabled = useIsWorkspaceActionsEnabled();
// If workspace actions are enabled we need to fetch the dormant
// workspaces as well. This lets us determine whether we should
// show a banner to the user indicating that some of their workspaces
// are at risk of being deleted.
useEffect(() => {
if (schedulingEnabled) {
const includesDormant = filterProps.filter.query.includes("is-dormant");
if (experimentEnabled) {
const includesDormant = filterProps.filter.query.includes("dormant_at");
const dormantQuery = includesDormant
? filterProps.filter.query
: filterProps.filter.query + " is-dormant:true";
@ -89,11 +89,12 @@ const WorkspacesPage: FC = () => {
// like dormant workspaces don't exist.
setDormantWorkspaces([]);
}
}, [schedulingEnabled, data, filterProps.filter.query]);
}, [experimentEnabled, data, filterProps.filter.query]);
const updateWorkspace = useWorkspaceUpdate(queryKey);
const [checkedWorkspaces, setCheckedWorkspaces] = useState<Workspace[]>([]);
const [isDeletingAll, setIsDeletingAll] = useState(false);
const [urlSearchParams] = searchParamsResult;
const { entitlements } = useDashboard();
const canCheckWorkspaces =
entitlements.features["workspace_batch_actions"].enabled;

View File

@ -1,7 +1,6 @@
import { FC } from "react";
import Box from "@mui/material/Box";
import { useDashboard } from "components/Dashboard/DashboardProvider";
import { useIsWorkspaceActionsEnabled } from "components/Dashboard/DashboardProvider";
import { Avatar, AvatarProps } from "components/Avatar/Avatar";
import { Palette, PaletteColor } from "@mui/material/styles";
import { TemplateFilterMenu, StatusFilterMenu } from "./menus";
@ -76,10 +75,8 @@ export const WorkspacesFilter = ({
error,
menus,
}: WorkspaceFilterProps) => {
const { entitlements } = useDashboard();
const actionsEnabled =
entitlements.features["advanced_template_scheduling"].enabled;
const presets = actionsEnabled ? PRESET_FILTERS : PRESETS_WITH_DORMANT;
const actionsEnabled = useIsWorkspaceActionsEnabled();
const presets = actionsEnabled ? PRESETS_WITH_DORMANT : PRESET_FILTERS;
return (
<Filter