From a9e5648557108cfd7006cd2e8985d787976cf7d9 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 21 Jun 2024 10:41:59 -0500 Subject: [PATCH 1/7] fix: remove connected button (#13625) It didn't make a lot of sense in current form. It will when we improve autostop. (cherry picked from commit 3ef12ac284a1fbb588f94c34357c9650e8109202) --- .../WorkspaceScheduleControls.tsx | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx b/site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx index 6b156c8fca38e..80fe42244f828 100644 --- a/site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx @@ -18,7 +18,6 @@ import { import type { Template, Workspace } from "api/typesGenerated"; import { TopbarData, TopbarIcon } from "components/FullPageLayout/Topbar"; import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; -import { Pill } from "components/Pill/Pill"; import { useTime } from "hooks/useTime"; import { getWorkspaceActivityStatus } from "modules/workspaces/activity"; import { @@ -170,11 +169,9 @@ const AutostopDisplay: FC = ({ const [showControlsAnyway, setShowControlsAnyway] = useState(false); let onClickScheduleIcon: (() => void) | undefined; - let activity: ReactNode = null; if (activityStatus === "connected") { onClickScheduleIcon = () => setShowControlsAnyway((it) => !it); - activity = Connected; const now = dayjs(); const noRequiredStopSoon = @@ -183,12 +180,7 @@ const AutostopDisplay: FC = ({ // User has shown controls manually, or we should warn about a nearby required stop if (!showControlsAnyway && noRequiredStopSoon) { - return ( - <> - {activity} - - - ); + return ; } } @@ -239,24 +231,18 @@ const AutostopDisplay: FC = ({ if (tooltip) { return ( - <> - {activity} - - {display} - {controls} - - + + {display} + {controls} + ); } return ( - <> - {activity} - - {display} - {controls} - - + + {display} + {controls} + ); }; From 0703fc68885946c424cd698955c4e5122ea73139 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 20 Jun 2024 16:33:47 -0500 Subject: [PATCH 2/7] fix: track login page correctly (#13618) (cherry picked from commit 495eea452fccb86585f5a3f90c069fe83b0c9e00) --- site/src/pages/LoginPage/LoginPage.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/site/src/pages/LoginPage/LoginPage.tsx b/site/src/pages/LoginPage/LoginPage.tsx index f05c0b40d981f..373da8e6e3408 100644 --- a/site/src/pages/LoginPage/LoginPage.tsx +++ b/site/src/pages/LoginPage/LoginPage.tsx @@ -1,4 +1,4 @@ -import type { FC } from "react"; +import { useEffect, type FC } from "react"; import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; import { Navigate, useLocation, useNavigate } from "react-router-dom"; @@ -24,10 +24,21 @@ export const LoginPage: FC = () => { const redirectTo = retrieveRedirect(location.search); const applicationName = getApplicationName(); const navigate = useNavigate(); - const { metadata } = useEmbeddedMetadata(); const buildInfoQuery = useQuery(buildInfo(metadata["build-info"])); + useEffect(() => { + if (!buildInfoQuery.data || isSignedIn) { + // isSignedIn already tracks with window.href! + return; + } + // This uses `navigator.sendBeacon`, so navigating away will not prevent it! + sendDeploymentEvent(buildInfoQuery.data, { + type: "deployment_login", + user_id: user?.id, + }); + }, [isSignedIn, buildInfoQuery.data, user?.id]); + if (isSignedIn) { // If the redirect is going to a workspace application, and we // are missing authentication, then we need to change the href location From b701620a014943ecfaa10724ea6bef804f02e7fd Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 20 Jun 2024 14:19:45 -0500 Subject: [PATCH 3/7] feat: add cross-origin reporting for telemetry in the dashboard (#13612) * feat: add cross-origin reporting for telemetry in the dashboard * Respect the telemetry flag * Fix embedded metadata * Fix compilation error * Fix linting (cherry picked from commit 0793a4b35bb73fd37e1d7e7214e7ae88fc742af5) --- coderd/apidoc/docs.go | 4 ++ coderd/apidoc/swagger.json | 4 ++ coderd/coderd.go | 1 + coderd/telemetry/telemetry.go | 6 +++ codersdk/deployment.go | 3 +- docs/api/general.md | 1 + docs/api/schemas.md | 2 + .../wsproxy/wsproxysdk/wsproxysdk_test.go | 2 +- site/jest.setup.ts | 1 + site/src/api/typesGenerated.ts | 1 + site/src/pages/LoginPage/LoginPage.tsx | 20 ++++++++++ site/src/pages/SetupPage/SetupPage.test.tsx | 40 ++++++++++++++++++- site/src/pages/SetupPage/SetupPage.tsx | 17 +++++++- site/src/testHelpers/entities.ts | 1 + site/src/utils/telemetry.ts | 33 +++++++++++++++ 15 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 site/src/utils/telemetry.ts diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index a284e46d0a0bb..6e460414f039b 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -8717,6 +8717,10 @@ const docTemplate = `{ "description": "ExternalURL references the current Coder version.\nFor production builds, this will link directly to a release. For development builds, this will link to a commit.", "type": "string" }, + "telemetry": { + "description": "Telemetry is a boolean that indicates whether telemetry is enabled.", + "type": "boolean" + }, "upgrade_message": { "description": "UpgradeMessage is the message displayed to users when an outdated client\nis detected.", "type": "string" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 28212bdaa8342..84ae4400c5eb1 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -7761,6 +7761,10 @@ "description": "ExternalURL references the current Coder version.\nFor production builds, this will link directly to a release. For development builds, this will link to a commit.", "type": "string" }, + "telemetry": { + "description": "Telemetry is a boolean that indicates whether telemetry is enabled.", + "type": "boolean" + }, "upgrade_message": { "description": "UpgradeMessage is the message displayed to users when an outdated client\nis detected.", "type": "string" diff --git a/coderd/coderd.go b/coderd/coderd.go index 25763530db702..befda4d3c78df 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -450,6 +450,7 @@ func New(options *Options) *API { WorkspaceProxy: false, UpgradeMessage: api.DeploymentValues.CLIUpgradeMessage.String(), DeploymentID: api.DeploymentID, + Telemetry: api.Telemetry.Enabled(), } api.SiteHandler = site.New(&site.Options{ BinFS: binFS, diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index 36292179da478..9b6c748d04d1d 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -100,6 +100,7 @@ type Reporter interface { // database. For example, if a new user is added, a snapshot can // contain just that user entry. Report(snapshot *Snapshot) + Enabled() bool Close() } @@ -116,6 +117,10 @@ type remoteReporter struct { shutdownAt *time.Time } +func (*remoteReporter) Enabled() bool { + return true +} + func (r *remoteReporter) Report(snapshot *Snapshot) { go r.reportSync(snapshot) } @@ -992,4 +997,5 @@ type Experiment struct { type noopReporter struct{} func (*noopReporter) Report(_ *Snapshot) {} +func (*noopReporter) Enabled() bool { return false } func (*noopReporter) Close() {} diff --git a/codersdk/deployment.go b/codersdk/deployment.go index c89a78668637d..4ee0980d15d0d 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -2162,11 +2162,12 @@ type BuildInfoResponse struct { ExternalURL string `json:"external_url"` // Version returns the semantic version of the build. Version string `json:"version"` - // DashboardURL is the URL to hit the deployment's dashboard. // For external workspace proxies, this is the coderd they are connected // to. DashboardURL string `json:"dashboard_url"` + // Telemetry is a boolean that indicates whether telemetry is enabled. + Telemetry bool `json:"telemetry"` WorkspaceProxy bool `json:"workspace_proxy"` diff --git a/docs/api/general.md b/docs/api/general.md index 52313409cb02c..0106a980f8d77 100644 --- a/docs/api/general.md +++ b/docs/api/general.md @@ -57,6 +57,7 @@ curl -X GET http://coder-server:8080/api/v2/buildinfo \ "dashboard_url": "string", "deployment_id": "string", "external_url": "string", + "telemetry": true, "upgrade_message": "string", "version": "string", "workspace_proxy": true diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 82804508b0e96..76564068b2add 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -1234,6 +1234,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "dashboard_url": "string", "deployment_id": "string", "external_url": "string", + "telemetry": true, "upgrade_message": "string", "version": "string", "workspace_proxy": true @@ -1248,6 +1249,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `dashboard_url` | string | false | | Dashboard URL is the URL to hit the deployment's dashboard. For external workspace proxies, this is the coderd they are connected to. | | `deployment_id` | string | false | | Deployment ID is the unique identifier for this deployment. | | `external_url` | string | false | | External URL references the current Coder version. For production builds, this will link directly to a release. For development builds, this will link to a commit. | +| `telemetry` | boolean | false | | Telemetry is a boolean that indicates whether telemetry is enabled. | | `upgrade_message` | string | false | | Upgrade message is the message displayed to users when an outdated client is detected. | | `version` | string | false | | Version returns the semantic version of the build. | | `workspace_proxy` | boolean | false | | | diff --git a/enterprise/wsproxy/wsproxysdk/wsproxysdk_test.go b/enterprise/wsproxy/wsproxysdk/wsproxysdk_test.go index 870d06b71da6d..c94b712cc9872 100644 --- a/enterprise/wsproxy/wsproxysdk/wsproxysdk_test.go +++ b/enterprise/wsproxy/wsproxysdk/wsproxysdk_test.go @@ -216,7 +216,7 @@ func TestDialCoordinator(t *testing.T) { Node: &proto.Node{ Id: 55, AsOf: timestamppb.New(time.Unix(1689653252, 0)), - Key: peerNodeKey[:], + Key: peerNodeKey, Disco: string(peerDiscoKey), PreferredDerp: 0, DerpLatency: map[string]float64{ diff --git a/site/jest.setup.ts b/site/jest.setup.ts index 6282295870681..40bb92fa44965 100644 --- a/site/jest.setup.ts +++ b/site/jest.setup.ts @@ -41,6 +41,7 @@ global.scrollTo = jest.fn(); window.HTMLElement.prototype.scrollIntoView = jest.fn(); window.open = jest.fn(); +navigator.sendBeacon = jest.fn(); // Polyfill the getRandomValues that is used on utils/random.ts Object.defineProperty(global.self, "crypto", { diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 88e5c7e508f67..2a5289f918379 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -171,6 +171,7 @@ export interface BuildInfoResponse { readonly external_url: string; readonly version: string; readonly dashboard_url: string; + readonly telemetry: boolean; readonly workspace_proxy: boolean; readonly agent_api_version: string; readonly upgrade_message: string; diff --git a/site/src/pages/LoginPage/LoginPage.tsx b/site/src/pages/LoginPage/LoginPage.tsx index 373da8e6e3408..d519a7673816b 100644 --- a/site/src/pages/LoginPage/LoginPage.tsx +++ b/site/src/pages/LoginPage/LoginPage.tsx @@ -8,6 +8,7 @@ import { useAuthContext } from "contexts/auth/AuthProvider"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { getApplicationName } from "utils/appearance"; import { retrieveRedirect } from "utils/redirect"; +import { sendDeploymentEvent } from "utils/telemetry"; import { LoginPageView } from "./LoginPageView"; export const LoginPage: FC = () => { @@ -19,6 +20,7 @@ export const LoginPage: FC = () => { signIn, isSigningIn, signInError, + user, } = useAuthContext(); const authMethodsQuery = useQuery(authMethods()); const redirectTo = retrieveRedirect(location.search); @@ -40,6 +42,15 @@ export const LoginPage: FC = () => { }, [isSignedIn, buildInfoQuery.data, user?.id]); if (isSignedIn) { + if (buildInfoQuery.data) { + // This uses `navigator.sendBeacon`, so window.href + // will not stop the request from being sent! + sendDeploymentEvent(buildInfoQuery.data, { + type: "deployment_login", + user_id: user?.id, + }); + } + // If the redirect is going to a workspace application, and we // are missing authentication, then we need to change the href location // to trigger a HTTP request. This allows the BE to generate the auth @@ -85,6 +96,15 @@ export const LoginPage: FC = () => { isSigningIn={isSigningIn} onSignIn={async ({ email, password }) => { await signIn(email, password); + if (buildInfoQuery.data) { + // This uses `navigator.sendBeacon`, so navigating away + // will not prevent it! + sendDeploymentEvent(buildInfoQuery.data, { + type: "deployment_login", + user_id: user?.id, + }); + } + navigate("/"); }} /> diff --git a/site/src/pages/SetupPage/SetupPage.test.tsx b/site/src/pages/SetupPage/SetupPage.test.tsx index 2f558316d95cc..fb22dcf4f303a 100644 --- a/site/src/pages/SetupPage/SetupPage.test.tsx +++ b/site/src/pages/SetupPage/SetupPage.test.tsx @@ -3,7 +3,7 @@ import userEvent from "@testing-library/user-event"; import { HttpResponse, http } from "msw"; import { createMemoryRouter } from "react-router-dom"; import type { Response, User } from "api/typesGenerated"; -import { MockUser } from "testHelpers/entities"; +import { MockBuildInfo, MockUser } from "testHelpers/entities"; import { renderWithRouter, waitForLoaderToBeRemoved, @@ -99,4 +99,42 @@ describe("Setup Page", () => { await fillForm(); await waitFor(() => screen.findByText("Templates")); }); + it("calls sendBeacon with telemetry", async () => { + const sendBeacon = jest.fn(); + Object.defineProperty(window.navigator, "sendBeacon", { + value: sendBeacon, + }); + renderWithRouter( + createMemoryRouter( + [ + { + path: "/setup", + element: , + }, + { + path: "/templates", + element:

Templates

, + }, + ], + { initialEntries: ["/setup"] }, + ), + ); + await waitForLoaderToBeRemoved(); + await waitFor(() => { + expect(navigator.sendBeacon).toBeCalledWith( + "https://coder.com/api/track-deployment", + new Blob( + [ + JSON.stringify({ + type: "deployment_setup", + deployment_id: MockBuildInfo.deployment_id, + }), + ], + { + type: "application/json", + }, + ), + ); + }); + }); }); diff --git a/site/src/pages/SetupPage/SetupPage.tsx b/site/src/pages/SetupPage/SetupPage.tsx index fc5d0cf35f957..20899157c3b30 100644 --- a/site/src/pages/SetupPage/SetupPage.tsx +++ b/site/src/pages/SetupPage/SetupPage.tsx @@ -1,11 +1,14 @@ -import type { FC } from "react"; +import { useEffect, type FC } from "react"; import { Helmet } from "react-helmet-async"; -import { useMutation } from "react-query"; +import { useMutation, useQuery } from "react-query"; import { Navigate, useNavigate } from "react-router-dom"; +import { buildInfo } from "api/queries/buildInfo"; import { createFirstUser } from "api/queries/users"; import { Loader } from "components/Loader/Loader"; import { useAuthContext } from "contexts/auth/AuthProvider"; +import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { pageTitle } from "utils/page"; +import { sendDeploymentEvent } from "utils/telemetry"; import { SetupPageView } from "./SetupPageView"; export const SetupPage: FC = () => { @@ -18,7 +21,17 @@ export const SetupPage: FC = () => { } = useAuthContext(); const createFirstUserMutation = useMutation(createFirstUser()); const setupIsComplete = !isConfiguringTheFirstUser; + const { metadata } = useEmbeddedMetadata(); + const buildInfoQuery = useQuery(buildInfo(metadata["build-info"])); const navigate = useNavigate(); + useEffect(() => { + if (!buildInfoQuery.data) { + return; + } + sendDeploymentEvent(buildInfoQuery.data, { + type: "deployment_setup", + }); + }, [buildInfoQuery.data]); if (isLoading) { return ; diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 1e2cf21e23383..e083c4c605b05 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -202,6 +202,7 @@ export const MockBuildInfo: TypesGen.BuildInfoResponse = { workspace_proxy: false, upgrade_message: "My custom upgrade message", deployment_id: "510d407f-e521-4180-b559-eab4a6d802b8", + telemetry: true, }; export const MockSupportLinks: TypesGen.LinkConfig[] = [ diff --git a/site/src/utils/telemetry.ts b/site/src/utils/telemetry.ts new file mode 100644 index 0000000000000..3b6906690cd6a --- /dev/null +++ b/site/src/utils/telemetry.ts @@ -0,0 +1,33 @@ +import type { BuildInfoResponse } from "api/typesGenerated"; + +// sendDeploymentEvent sends a CORs payload to coder.com +// to track a deployment event. +export const sendDeploymentEvent = ( + buildInfo: BuildInfoResponse, + payload: { + type: "deployment_setup" | "deployment_login"; + user_id?: string; + }, +) => { + if (typeof navigator === "undefined" || !navigator.sendBeacon) { + // It's fine if we don't report this, it's not required! + return; + } + if (!buildInfo.telemetry) { + return; + } + navigator.sendBeacon( + "https://coder.com/api/track-deployment", + new Blob( + [ + JSON.stringify({ + ...payload, + deployment_id: buildInfo.deployment_id, + }), + ], + { + type: "application/json", + }, + ), + ); +}; From 201cb1cbedcc5b84c5b5c327e9b1a060846104f5 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 19 Jun 2024 12:02:51 -0400 Subject: [PATCH 4/7] fix: display trial errors in the dashboard (#13601) * fix: display trial errors in the dashboard The error was essentially being ignored before! * Remove day mention in product of trial * fmt (cherry picked from commit 7049d7a8816c7d2d2eff7769d8a718ec8aca6e17) --- cli/login.go | 2 +- enterprise/trialer/trialer.go | 16 +++++++++++++++ .../pages/SetupPage/SetupPageView.stories.tsx | 9 +++++++++ site/src/pages/SetupPage/SetupPageView.tsx | 20 ++++++++++++++++++- 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/cli/login.go b/cli/login.go index 65a94d8a4ec3e..87cfea103c271 100644 --- a/cli/login.go +++ b/cli/login.go @@ -239,7 +239,7 @@ func (r *RootCmd) login() *serpent.Command { if !inv.ParsedFlags().Changed("first-user-trial") && os.Getenv(firstUserTrialEnv) == "" { v, _ := cliui.Prompt(inv, cliui.PromptOptions{ - Text: "Start a 30-day trial of Enterprise?", + Text: "Start a trial of Enterprise?", IsConfirm: true, Default: "yes", }) diff --git a/enterprise/trialer/trialer.go b/enterprise/trialer/trialer.go index fd846df58db61..fa5d15a65b25a 100644 --- a/enterprise/trialer/trialer.go +++ b/enterprise/trialer/trialer.go @@ -39,6 +39,22 @@ func New(db database.Store, url string, keys map[string]ed25519.PublicKey) func( return xerrors.Errorf("perform license request: %w", err) } defer res.Body.Close() + if res.StatusCode > 300 { + body, err := io.ReadAll(res.Body) + if err != nil { + return xerrors.Errorf("read license response: %w", err) + } + // This is the format of the error response from + // the license server. + var msg struct { + Error string `json:"error"` + } + err = json.Unmarshal(body, &msg) + if err != nil { + return xerrors.Errorf("unmarshal error: %w", err) + } + return xerrors.New(msg.Error) + } raw, err := io.ReadAll(res.Body) if err != nil { return xerrors.Errorf("read license: %w", err) diff --git a/site/src/pages/SetupPage/SetupPageView.stories.tsx b/site/src/pages/SetupPage/SetupPageView.stories.tsx index 239fb10cab930..030115fbbddb9 100644 --- a/site/src/pages/SetupPage/SetupPageView.stories.tsx +++ b/site/src/pages/SetupPage/SetupPageView.stories.tsx @@ -22,6 +22,15 @@ export const FormError: Story = { }, }; +export const TrialError: Story = { + args: { + error: mockApiError({ + message: "Couldn't generate trial!", + detail: "It looks like your team is already trying Coder.", + }), + }, +}; + export const Loading: Story = { args: { isLoading: true, diff --git a/site/src/pages/SetupPage/SetupPageView.tsx b/site/src/pages/SetupPage/SetupPageView.tsx index af673acacc333..7f97deb973991 100644 --- a/site/src/pages/SetupPage/SetupPageView.tsx +++ b/site/src/pages/SetupPage/SetupPageView.tsx @@ -1,13 +1,16 @@ import LoadingButton from "@mui/lab/LoadingButton"; +import AlertTitle from "@mui/material/AlertTitle"; import Autocomplete from "@mui/material/Autocomplete"; import Checkbox from "@mui/material/Checkbox"; import Link from "@mui/material/Link"; import MenuItem from "@mui/material/MenuItem"; import TextField from "@mui/material/TextField"; +import { isAxiosError } from "axios"; import { type FormikContextType, useFormik } from "formik"; import type { FC } from "react"; import * as Yup from "yup"; import type * as TypesGen from "api/typesGenerated"; +import { Alert, AlertDetail } from "components/Alert/Alert"; import { FormFields, VerticalForm } from "components/Form/Form"; import { CoderIcon } from "components/Icons/CoderIcon"; import { SignInLayout } from "components/SignInLayout/SignInLayout"; @@ -187,7 +190,7 @@ export const SetupPageView: FC = ({
- Start a 30-day free trial of Enterprise + Start a free trial of Enterprise ({ @@ -316,6 +319,21 @@ export const SetupPageView: FC = ({ )} + {isAxiosError(error) && error.response?.data?.message && ( + + {error.response.data.message} + {error.response.data.detail && ( + + {error.response.data.detail} +
+ + Contact Sales + +
+ )} +
+ )} + Date: Tue, 18 Jun 2024 16:20:21 -0400 Subject: [PATCH 5/7] fix: write server config to telemetry (#13590) * fix: add external auth configs to telemetry * Refactor telemetry to send the entire config * gen * Fix linting (cherry picked from commit 3a1fa0459090b9bbb92a482a0c73795b2e1b05e1) --- cli/server.go | 33 ++--- coderd/apidoc/docs.go | 6 - coderd/apidoc/swagger.json | 6 - coderd/telemetry/telemetry.go | 130 ++++++------------ coderd/telemetry/telemetry_test.go | 11 -- codersdk/deployment.go | 2 +- docs/api/general.md | 1 - docs/api/schemas.md | 5 - site/src/api/typesGenerated.ts | 1 - .../ExternalAuthSettingsPageView.stories.tsx | 1 - 10 files changed, 54 insertions(+), 142 deletions(-) diff --git a/cli/server.go b/cli/server.go index 3706b2ee1bc92..612972014fbb7 100644 --- a/cli/server.go +++ b/cli/server.go @@ -796,31 +796,18 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. cliui.Infof(inv.Stdout, "\n==> Logs will stream in below (press ctrl+c to gracefully exit):") if vals.Telemetry.Enable { - gitAuth := make([]telemetry.GitAuth, 0) - // TODO: - var gitAuthConfigs []codersdk.ExternalAuthConfig - for _, cfg := range gitAuthConfigs { - gitAuth = append(gitAuth, telemetry.GitAuth{ - Type: cfg.Type, - }) + vals, err := vals.WithoutSecrets() + if err != nil { + return xerrors.Errorf("remove secrets from deployment values: %w", err) } - options.Telemetry, err = telemetry.New(telemetry.Options{ - BuiltinPostgres: builtinPostgres, - DeploymentID: deploymentID, - Database: options.Database, - Logger: logger.Named("telemetry"), - URL: vals.Telemetry.URL.Value(), - Wildcard: vals.WildcardAccessURL.String() != "", - DERPServerRelayURL: vals.DERP.Server.RelayURL.String(), - GitAuth: gitAuth, - GitHubOAuth: vals.OAuth2.Github.ClientID != "", - OIDCAuth: vals.OIDC.ClientID != "", - OIDCIssuerURL: vals.OIDC.IssuerURL.String(), - Prometheus: vals.Prometheus.Enable.Value(), - STUN: len(vals.DERP.Server.STUNAddresses) != 0, - Tunnel: tunnel != nil, - Experiments: vals.Experiments.Value(), + BuiltinPostgres: builtinPostgres, + DeploymentID: deploymentID, + Database: options.Database, + Logger: logger.Named("telemetry"), + URL: vals.Telemetry.URL.Value(), + Tunnel: tunnel != nil, + DeploymentConfig: vals, ParseLicenseJWT: func(lic *telemetry.License) error { // This will be nil when running in AGPL-only mode. if options.ParseLicenseClaims == nil { diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 6e460414f039b..ad5acf06e34c8 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -9786,12 +9786,6 @@ const docTemplate = `{ "description": "DisplayName is shown in the UI to identify the auth config.", "type": "string" }, - "extra_token_keys": { - "type": "array", - "items": { - "type": "string" - } - }, "id": { "description": "ID is a unique identifier for the auth config.\nIt defaults to ` + "`" + `type` + "`" + ` when not provided.", "type": "string" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 84ae4400c5eb1..33090f548dada 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -8773,12 +8773,6 @@ "description": "DisplayName is shown in the UI to identify the auth config.", "type": "string" }, - "extra_token_keys": { - "type": "array", - "items": { - "type": "string" - } - }, "id": { "description": "ID is a unique identifier for the auth config.\nIt defaults to `type` when not provided.", "type": "string" diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index 9b6c748d04d1d..9d16ba7922098 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -41,20 +41,13 @@ type Options struct { // URL is an endpoint to direct telemetry towards! URL *url.URL - BuiltinPostgres bool - DeploymentID string - GitHubOAuth bool - OIDCAuth bool - OIDCIssuerURL string - Wildcard bool - DERPServerRelayURL string - GitAuth []GitAuth - Prometheus bool - STUN bool - SnapshotFrequency time.Duration - Tunnel bool - ParseLicenseJWT func(lic *License) error - Experiments []string + DeploymentID string + DeploymentConfig *codersdk.DeploymentValues + BuiltinPostgres bool + Tunnel bool + + SnapshotFrequency time.Duration + ParseLicenseJWT func(lic *License) error } // New constructs a reporter for telemetry data. @@ -247,31 +240,24 @@ func (r *remoteReporter) deployment() error { } data, err := json.Marshal(&Deployment{ - ID: r.options.DeploymentID, - Architecture: sysInfo.Architecture, - BuiltinPostgres: r.options.BuiltinPostgres, - Containerized: containerized, - Wildcard: r.options.Wildcard, - DERPServerRelayURL: r.options.DERPServerRelayURL, - GitAuth: r.options.GitAuth, - Kubernetes: os.Getenv("KUBERNETES_SERVICE_HOST") != "", - GitHubOAuth: r.options.GitHubOAuth, - OIDCAuth: r.options.OIDCAuth, - OIDCIssuerURL: r.options.OIDCIssuerURL, - Prometheus: r.options.Prometheus, - InstallSource: installSource, - STUN: r.options.STUN, - Tunnel: r.options.Tunnel, - OSType: sysInfo.OS.Type, - OSFamily: sysInfo.OS.Family, - OSPlatform: sysInfo.OS.Platform, - OSName: sysInfo.OS.Name, - OSVersion: sysInfo.OS.Version, - CPUCores: runtime.NumCPU(), - MemoryTotal: mem.Total, - MachineID: sysInfo.UniqueID, - StartedAt: r.startedAt, - ShutdownAt: r.shutdownAt, + ID: r.options.DeploymentID, + Architecture: sysInfo.Architecture, + BuiltinPostgres: r.options.BuiltinPostgres, + Containerized: containerized, + Config: r.options.DeploymentConfig, + Kubernetes: os.Getenv("KUBERNETES_SERVICE_HOST") != "", + InstallSource: installSource, + Tunnel: r.options.Tunnel, + OSType: sysInfo.OS.Type, + OSFamily: sysInfo.OS.Family, + OSPlatform: sysInfo.OS.Platform, + OSName: sysInfo.OS.Name, + OSVersion: sysInfo.OS.Version, + CPUCores: runtime.NumCPU(), + MemoryTotal: mem.Total, + MachineID: sysInfo.UniqueID, + StartedAt: r.startedAt, + ShutdownAt: r.shutdownAt, }) if err != nil { return xerrors.Errorf("marshal deployment: %w", err) @@ -486,10 +472,6 @@ func (r *remoteReporter) createSnapshot() (*Snapshot, error) { } return nil }) - eg.Go(func() error { - snapshot.Experiments = ConvertExperiments(r.options.Experiments) - return nil - }) err := eg.Wait() if err != nil { @@ -750,16 +732,6 @@ func ConvertExternalProvisioner(id uuid.UUID, tags map[string]string, provisione } } -func ConvertExperiments(experiments []string) []Experiment { - var out []Experiment - - for _, exp := range experiments { - out = append(out, Experiment{Name: exp}) - } - - return out -} - // Snapshot represents a point-in-time anonymized database dump. // Data is aggregated by latest on the server-side, so partial data // can be sent without issue. @@ -782,40 +754,28 @@ type Snapshot struct { WorkspaceResourceMetadata []WorkspaceResourceMetadata `json:"workspace_resource_metadata"` WorkspaceResources []WorkspaceResource `json:"workspace_resources"` Workspaces []Workspace `json:"workspaces"` - Experiments []Experiment `json:"experiments"` } // Deployment contains information about the host running Coder. type Deployment struct { - ID string `json:"id"` - Architecture string `json:"architecture"` - BuiltinPostgres bool `json:"builtin_postgres"` - Containerized bool `json:"containerized"` - Kubernetes bool `json:"kubernetes"` - Tunnel bool `json:"tunnel"` - Wildcard bool `json:"wildcard"` - DERPServerRelayURL string `json:"derp_server_relay_url"` - GitAuth []GitAuth `json:"git_auth"` - GitHubOAuth bool `json:"github_oauth"` - OIDCAuth bool `json:"oidc_auth"` - OIDCIssuerURL string `json:"oidc_issuer_url"` - Prometheus bool `json:"prometheus"` - InstallSource string `json:"install_source"` - STUN bool `json:"stun"` - OSType string `json:"os_type"` - OSFamily string `json:"os_family"` - OSPlatform string `json:"os_platform"` - OSName string `json:"os_name"` - OSVersion string `json:"os_version"` - CPUCores int `json:"cpu_cores"` - MemoryTotal uint64 `json:"memory_total"` - MachineID string `json:"machine_id"` - StartedAt time.Time `json:"started_at"` - ShutdownAt *time.Time `json:"shutdown_at"` -} - -type GitAuth struct { - Type string `json:"type"` + ID string `json:"id"` + Architecture string `json:"architecture"` + BuiltinPostgres bool `json:"builtin_postgres"` + Containerized bool `json:"containerized"` + Kubernetes bool `json:"kubernetes"` + Config *codersdk.DeploymentValues `json:"config"` + Tunnel bool `json:"tunnel"` + InstallSource string `json:"install_source"` + OSType string `json:"os_type"` + OSFamily string `json:"os_family"` + OSPlatform string `json:"os_platform"` + OSName string `json:"os_name"` + OSVersion string `json:"os_version"` + CPUCores int `json:"cpu_cores"` + MemoryTotal uint64 `json:"memory_total"` + MachineID string `json:"machine_id"` + StartedAt time.Time `json:"started_at"` + ShutdownAt *time.Time `json:"shutdown_at"` } type APIKey struct { @@ -990,10 +950,6 @@ type ExternalProvisioner struct { ShutdownAt *time.Time `json:"shutdown_at"` } -type Experiment struct { - Name string `json:"name"` -} - type noopReporter struct{} func (*noopReporter) Report(_ *Snapshot) {} diff --git a/coderd/telemetry/telemetry_test.go b/coderd/telemetry/telemetry_test.go index 4661a4f8f21bf..fa8650de3f3d5 100644 --- a/coderd/telemetry/telemetry_test.go +++ b/coderd/telemetry/telemetry_test.go @@ -114,17 +114,6 @@ func TestTelemetry(t *testing.T) { require.Len(t, snapshot.Users, 1) require.Equal(t, snapshot.Users[0].EmailHashed, "bb44bf07cf9a2db0554bba63a03d822c927deae77df101874496df5a6a3e896d@coder.com") }) - t.Run("Experiments", func(t *testing.T) { - t.Parallel() - - const expName = "my-experiment" - exps := []string{expName} - _, snapshot := collectSnapshot(t, dbmem.New(), func(opts telemetry.Options) telemetry.Options { - opts.Experiments = exps - return opts - }) - require.Equal(t, []telemetry.Experiment{{Name: expName}}, snapshot.Experiments) - }) } // nolint:paralleltest diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 4ee0980d15d0d..2969d2cfd745d 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -392,7 +392,7 @@ type ExternalAuthConfig struct { AppInstallationsURL string `json:"app_installations_url" yaml:"app_installations_url"` NoRefresh bool `json:"no_refresh" yaml:"no_refresh"` Scopes []string `json:"scopes" yaml:"scopes"` - ExtraTokenKeys []string `json:"extra_token_keys" yaml:"extra_token_keys"` + ExtraTokenKeys []string `json:"-" yaml:"extra_token_keys"` DeviceFlow bool `json:"device_flow" yaml:"device_flow"` DeviceCodeURL string `json:"device_code_url" yaml:"device_code_url"` // Regex allows API requesters to match an auth config by diff --git a/docs/api/general.md b/docs/api/general.md index 0106a980f8d77..aa77d9d272c94 100644 --- a/docs/api/general.md +++ b/docs/api/general.md @@ -228,7 +228,6 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "device_flow": true, "display_icon": "string", "display_name": "string", - "extra_token_keys": ["string"], "id": "string", "no_refresh": true, "regex": "string", diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 76564068b2add..6f6b6ae87d3df 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -2011,7 +2011,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "device_flow": true, "display_icon": "string", "display_name": "string", - "extra_token_keys": ["string"], "id": "string", "no_refresh": true, "regex": "string", @@ -2384,7 +2383,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "device_flow": true, "display_icon": "string", "display_name": "string", - "extra_token_keys": ["string"], "id": "string", "no_refresh": true, "regex": "string", @@ -2803,7 +2801,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "device_flow": true, "display_icon": "string", "display_name": "string", - "extra_token_keys": ["string"], "id": "string", "no_refresh": true, "regex": "string", @@ -2826,7 +2823,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `device_flow` | boolean | false | | | | `display_icon` | string | false | | Display icon is a URL to an icon to display in the UI. | | `display_name` | string | false | | Display name is shown in the UI to identify the auth config. | -| `extra_token_keys` | array of string | false | | | | `id` | string | false | | ID is a unique identifier for the auth config. It defaults to `type` when not provided. | | `no_refresh` | boolean | false | | | | `regex` | string | false | | Regex allows API requesters to match an auth config by a string (e.g. coder.com) instead of by it's type. | @@ -8846,7 +8842,6 @@ _None_ "device_flow": true, "display_icon": "string", "display_name": "string", - "extra_token_keys": ["string"], "id": "string", "no_refresh": true, "regex": "string", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 2a5289f918379..3bf37219ea1e8 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -507,7 +507,6 @@ export interface ExternalAuthConfig { readonly app_installations_url: string; readonly no_refresh: boolean; readonly scopes: readonly string[]; - readonly extra_token_keys: readonly string[]; readonly device_flow: boolean; readonly device_code_url: string; readonly regex: string; diff --git a/site/src/pages/DeploySettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.stories.tsx b/site/src/pages/DeploySettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.stories.tsx index 22a57140511f5..3ed1d8143738e 100644 --- a/site/src/pages/DeploySettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.stories.tsx @@ -19,7 +19,6 @@ const meta: Meta = { app_installations_url: "", no_refresh: false, scopes: [], - extra_token_keys: [], device_flow: true, device_code_url: "", display_icon: "", From ae220f52e7675de8f17ba72ed227c2d09b94823b Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 4 Jun 2024 20:10:15 +0300 Subject: [PATCH 6/7] chore(scripts): fix dry run for autoversion in release.sh (#13470) (cherry picked from commit 3b7f9534fbce20b8bf887b624e1bc144a341b2be) --- scripts/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index 66c30a6792821..ec887cd9c3dc1 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -374,7 +374,7 @@ You can follow the release progress [here](https://github.com/coder/coder/action create_pr_stash=1 fi maybedryrun "${dry_run}" git checkout -b "${pr_branch}" "${remote}/${branch}" - execrelative go run ./release autoversion --channel "${channel}" "${new_version}" --dry-run + execrelative go run ./release autoversion --channel "${channel}" "${new_version}" --dry-run="${dry_run}" maybedryrun "${dry_run}" git add docs maybedryrun "${dry_run}" git commit -m "${title}" # Return to previous branch. From e54ff57a9a89a83164fca391b3ae1ea906b99eb8 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 5 Jun 2024 02:01:26 +0300 Subject: [PATCH 7/7] chore(scripts): fix release promote stable to set latest tag (#13471) (cherry picked from commit 9a757f8e74ae062a81ad365f95d67f24de77d08a) --- scripts/release/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/release/main.go b/scripts/release/main.go index 8eaeb20825a92..3eb67c7b96225 100644 --- a/scripts/release/main.go +++ b/scripts/release/main.go @@ -242,6 +242,7 @@ func (r *releaseCommand) promoteVersionToStable(ctx context.Context, inv *serpen updatedBody := removeMainlineBlurb(newStable.GetBody()) updatedBody = addStableSince(time.Now().UTC(), updatedBody) updatedNewStable.Body = github.String(updatedBody) + updatedNewStable.MakeLatest = github.String("true") updatedNewStable.Prerelease = github.Bool(false) updatedNewStable.Draft = github.Bool(false) if !r.dryRun { 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