diff --git a/coderd/coderd.go b/coderd/coderd.go index 4507cd1dd7605..a4c4d335d738f 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -972,7 +972,7 @@ func New(options *Options) *API { }) r.Route("/experiments", func(r chi.Router) { r.Use(apiKeyMiddleware) - r.Get("/available", handleExperimentsSafe) + r.Get("/available", handleExperimentsAvailable) r.Get("/", api.handleExperimentsGet) }) r.Get("/updatecheck", api.updateCheck) @@ -1895,7 +1895,9 @@ func ReadExperiments(log slog.Logger, raw []string) codersdk.Experiments { exps = append(exps, codersdk.ExperimentsSafe...) default: ex := codersdk.Experiment(strings.ToLower(v)) - if !slice.Contains(codersdk.ExperimentsSafe, ex) { + if !slice.Contains(codersdk.ExperimentsKnown, ex) { + log.Warn(context.Background(), "ignoring unknown experiment", slog.F("experiment", ex)) + } else if !slice.Contains(codersdk.ExperimentsSafe, ex) { log.Warn(context.Background(), "🐉 HERE BE DRAGONS: opting into hidden experiment", slog.F("experiment", ex)) } exps = append(exps, ex) diff --git a/coderd/experiments.go b/coderd/experiments.go index 6f03daa4e9d88..a0949e9411664 100644 --- a/coderd/experiments.go +++ b/coderd/experiments.go @@ -26,7 +26,7 @@ func (api *API) handleExperimentsGet(rw http.ResponseWriter, r *http.Request) { // @Tags General // @Success 200 {array} codersdk.Experiment // @Router /experiments/available [get] -func handleExperimentsSafe(rw http.ResponseWriter, r *http.Request) { +func handleExperimentsAvailable(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() httpapi.Write(ctx, rw, http.StatusOK, codersdk.AvailableExperiments{ Safe: codersdk.ExperimentsSafe, diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 90e8a4c879ec5..ce15ee407a8f3 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -3372,6 +3372,18 @@ const ( ExperimentAITasks Experiment = "ai-tasks" // Enables the new AI tasks feature. ) +// ExperimentsKnown should include all experiments defined above. +var ExperimentsKnown = Experiments{ + ExperimentExample, + ExperimentAutoFillParameters, + ExperimentNotifications, + ExperimentWorkspaceUsage, + ExperimentWebPush, + ExperimentWorkspacePrebuilds, + ExperimentAgenticChat, + ExperimentAITasks, +} + // ExperimentsSafe should include all experiments that are safe for // users to opt-in to via --experimental='*'. // Experiments that are not ready for consumption by all users should @@ -3384,6 +3396,9 @@ var ExperimentsSafe = Experiments{ // Multiple experiments may be enabled at the same time. // Experiments are not safe for production use, and are not guaranteed to // be backwards compatible. They may be removed or renamed at any time. +// The below typescript-ignore annotation allows our typescript generator +// to generate an enum list, which is used in the frontend. +// @typescript-ignore Experiments type Experiments []Experiment // Returns a list of experiments that are enabled for the deployment. diff --git a/site/src/api/queries/experiments.ts b/site/src/api/queries/experiments.ts index 546a85ab5f083..fe7e3419a7065 100644 --- a/site/src/api/queries/experiments.ts +++ b/site/src/api/queries/experiments.ts @@ -1,11 +1,11 @@ import { API } from "api/api"; -import type { Experiments } from "api/typesGenerated"; +import { type Experiment, Experiments } from "api/typesGenerated"; import type { MetadataState } from "hooks/useEmbeddedMetadata"; import { cachedQuery } from "./util"; const experimentsKey = ["experiments"] as const; -export const experiments = (metadata: MetadataState) => { +export const experiments = (metadata: MetadataState) => { return cachedQuery({ metadata, queryKey: experimentsKey, @@ -19,3 +19,7 @@ export const availableExperiments = () => { queryFn: async () => API.getAvailableExperiments(), }; }; + +export const isKnownExperiment = (experiment: string): boolean => { + return Experiments.includes(experiment as Experiment); +}; diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 9a5acb4fbe569..485c7d25d859d 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -835,8 +835,16 @@ export type Experiment = | "workspace-prebuilds" | "workspace-usage"; -// From codersdk/deployment.go -export type Experiments = readonly Experiment[]; +export const Experiments: Experiment[] = [ + "ai-tasks", + "agentic-chat", + "auto-fill-parameters", + "example", + "notifications", + "web-push", + "workspace-prebuilds", + "workspace-usage", +]; // From codersdk/externalauth.go export interface ExternalAuth { diff --git a/site/src/hooks/useEmbeddedMetadata.ts b/site/src/hooks/useEmbeddedMetadata.ts index 1dd2d7c2bbeeb..908d89c9590e5 100644 --- a/site/src/hooks/useEmbeddedMetadata.ts +++ b/site/src/hooks/useEmbeddedMetadata.ts @@ -2,7 +2,7 @@ import type { AppearanceConfig, BuildInfoResponse, Entitlements, - Experiments, + Experiment, Region, User, UserAppearanceSettings, @@ -24,7 +24,7 @@ export const DEFAULT_METADATA_KEY = "property"; */ type AvailableMetadata = Readonly<{ user: User; - experiments: Experiments; + experiments: Experiment[]; appearance: AppearanceConfig; userAppearance: UserAppearanceSettings; entitlements: Entitlements; @@ -89,7 +89,7 @@ export class MetadataManager implements MetadataManagerApi { userAppearance: this.registerValue("userAppearance"), entitlements: this.registerValue("entitlements"), - experiments: this.registerValue("experiments"), + experiments: this.registerValue("experiments"), "build-info": this.registerValue("build-info"), regions: this.registerRegionValue(), tasksTabVisible: this.registerValue("tasksTabVisible"), diff --git a/site/src/modules/dashboard/DashboardProvider.tsx b/site/src/modules/dashboard/DashboardProvider.tsx index d56e30afaed8b..7eae04befa24a 100644 --- a/site/src/modules/dashboard/DashboardProvider.tsx +++ b/site/src/modules/dashboard/DashboardProvider.tsx @@ -5,7 +5,7 @@ import { organizations } from "api/queries/organizations"; import type { AppearanceConfig, Entitlements, - Experiments, + Experiment, Organization, } from "api/typesGenerated"; import { ErrorAlert } from "components/Alert/ErrorAlert"; @@ -19,7 +19,7 @@ import { selectFeatureVisibility } from "./entitlements"; export interface DashboardValue { entitlements: Entitlements; - experiments: Experiments; + experiments: Experiment[]; appearance: AppearanceConfig; organizations: readonly Organization[]; showOrganizations: boolean; diff --git a/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPage.tsx b/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPage.tsx index fc15eca1ec4f1..c4f49e1dda31a 100644 --- a/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPage.tsx +++ b/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPage.tsx @@ -1,5 +1,9 @@ import { deploymentDAUs } from "api/queries/deployment"; -import { availableExperiments, experiments } from "api/queries/experiments"; +import { + availableExperiments, + experiments, + isKnownExperiment, +} from "api/queries/experiments"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { useDeploymentConfig } from "modules/management/DeploymentConfigProvider"; import type { FC } from "react"; @@ -18,7 +22,7 @@ const OverviewPage: FC = () => { const safeExperiments = safeExperimentsQuery.data?.safe ?? []; const invalidExperiments = enabledExperimentsQuery.data?.filter((exp) => { - return !safeExperiments.includes(exp); + return !isKnownExperiment(exp); }) ?? []; const { data: dailyActiveUsers } = useQuery(deploymentDAUs()); diff --git a/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.stories.tsx b/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.stories.tsx index 3535d4ffd1d47..24e121b9ff0f5 100644 --- a/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.stories.tsx +++ b/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.stories.tsx @@ -30,7 +30,7 @@ const meta: Meta = { description: "Enable one or more experiments. These are not ready for production. Separate multiple experiments with commas, or enter '*' to opt-in to all available experiments.", flag: "experiments", - value: ["workspace_actions"], + value: ["example"], flag_shorthand: "", hidden: false, }, @@ -82,8 +82,8 @@ export const allExperimentsEnabled: Story = { hidden: false, }, ], - safeExperiments: ["shared-ports"], - invalidExperiments: ["invalid"], + safeExperiments: ["example"], + invalidExperiments: [], }, }; @@ -118,7 +118,7 @@ export const invalidExperimentsEnabled: Story = { hidden: false, }, ], - safeExperiments: ["shared-ports"], + safeExperiments: ["example"], invalidExperiments: ["invalid"], }, }; diff --git a/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.tsx b/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.tsx index 505036a9c821f..37da47f4b8a16 100644 --- a/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/OverviewPage/OverviewPageView.tsx @@ -1,7 +1,7 @@ import AlertTitle from "@mui/material/AlertTitle"; import type { DAUsResponse, - Experiments, + Experiment, SerpentOption, } from "api/typesGenerated"; import { Link } from "components/Link/Link"; @@ -22,8 +22,8 @@ import { UserEngagementChart } from "./UserEngagementChart"; type OverviewPageViewProps = { deploymentOptions: SerpentOption[]; dailyActiveUsers: DAUsResponse | undefined; - readonly invalidExperiments: Experiments | string[]; - readonly safeExperiments: Experiments | string[]; + readonly invalidExperiments: readonly string[]; + readonly safeExperiments: readonly Experiment[]; }; export const OverviewPageView: FC = ({ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy