From 6e4508e29ce0fdce586c00df695c34f1000af7c2 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 20 Jun 2025 15:05:22 -0500 Subject: [PATCH 1/5] chore: assume template versions without tf values to be empty (#18479) Closes https://github.com/coder/internal/issues/735 --- coderd/dynamicparameters/render.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/coderd/dynamicparameters/render.go b/coderd/dynamicparameters/render.go index b6a77c4704225..8e7df929505f1 100644 --- a/coderd/dynamicparameters/render.go +++ b/coderd/dynamicparameters/render.go @@ -2,9 +2,11 @@ package dynamicparameters import ( "context" + "database/sql" "io/fs" "log/slog" "sync" + "time" "github.com/google/uuid" "golang.org/x/sync/errgroup" @@ -105,9 +107,24 @@ func (r *loader) loadData(ctx context.Context, db database.Store) error { if r.terraformValues == nil { values, err := db.GetTemplateVersionTerraformValues(ctx, r.templateVersion.ID) - if err != nil { + if err != nil && !xerrors.Is(err, sql.ErrNoRows) { return xerrors.Errorf("template version terraform values: %w", err) } + + if xerrors.Is(err, sql.ErrNoRows) { + // If the row does not exist, return zero values. + // + // Older template versions (prior to dynamic parameters) will be missing + // this row, and we can assume the 'ProvisionerdVersion' "" (unknown). + values = database.TemplateVersionTerraformValue{ + TemplateVersionID: r.templateVersionID, + UpdatedAt: time.Time{}, + CachedPlan: nil, + CachedModuleFiles: uuid.NullUUID{}, + ProvisionerdVersion: "", + } + } + r.terraformValues = &values } From cbe462789356d76099c3a40f641dc58fca79a104 Mon Sep 17 00:00:00 2001 From: Edward Angert Date: Fri, 20 Jun 2025 17:13:36 -0400 Subject: [PATCH 2/5] docs: document how to tag coder users in dx data cloud (#17805) [preview](https://coder.com/docs/@tag-coder-users-dx/admin/integrations/data-cloud) --------- Co-authored-by: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> --- docs/admin/integrations/dx-data-cloud.md | 87 ++++++++++++++++++++++++ docs/manifest.json | 5 ++ 2 files changed, 92 insertions(+) create mode 100644 docs/admin/integrations/dx-data-cloud.md diff --git a/docs/admin/integrations/dx-data-cloud.md b/docs/admin/integrations/dx-data-cloud.md new file mode 100644 index 0000000000000..62055d69f5f1a --- /dev/null +++ b/docs/admin/integrations/dx-data-cloud.md @@ -0,0 +1,87 @@ +# DX Data Cloud + +[DX](https://getdx.com) is a developer intelligence platform used by engineering +leaders and platform engineers. + +DX uses metadata attributes to assign information to individual users. +While it's common to segment users by `role`, `level`, or `geo`, it’s become increasingly +common to use DX attributes to better understand usage and adoption of tools. + +You can create a `Coder` attribute in DX to segment and analyze the impact of Coder usage on a developer’s work, including: + +- Understanding the needs of power users or low Coder usage across the org +- Correlate Coder usage with qualitative and quantitative engineering metrics, + such as PR throughput, deployment frequency, deep work, dev environment toil, and more. +- Personalize user experiences + +## Requirements + +- A DX subscription +- Access to Coder user data through the Coder CLI, Coder API, an IdP, or an existing Coder-DX integration +- Coordination with your DX Customer Success Manager + +## Extract Your Coder User List + +
+ +You can use the Coder CLI, Coder API, or your Identity Provider (IdP) to extract your list of users. + +If your organization already uses the Coder-DX integration, you can find a list of active Coder users directly within DX. + +### CLI + +Use `users list` to export the list of users to a CSV file: + +```shell +coder users list > users.csv +``` + +Visit the [users list](../../reference/cli/users_list.md) documentation for more options. + +### API + +Use [get users](../../reference/api/users.md#get-users): + +```bash +curl -X GET http://coder-server:8080/api/v2/users \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +To export the results to a CSV file, you can use the `jq` tool to process the JSON response: + +```bash +curl -X GET http://coder-server:8080/api/v2/users \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' | \ + jq -r '.users | (map(keys) | add | unique) as $cols | $cols, (.[] | [.[$cols[]]] | @csv)' > users.csv +``` + +Visit the [get users](../../reference/api/users.md#get-users) documentation for more options. + +### IdP + +If your organization uses a centralized IdP to manage user accounts, you can extract user data directly from your IdP. + +This is particularly useful if you need additional user attributes managed within your IdP. + +
+ +## Contact your DX Customer Success Manager + +Provide the file to your dedicated DX Customer Success Manager (CSM). + +Your CSM will import the CSV of individuals using Coder, as well as usage frequency (if applicable) into DX to create a `Coder` attribute. + +After the attribute is uploaded, you'll have a Coder filter option within your DX reports allowing you to: + +- Perform cohort analysis (Coder user vs non-user) +- Understand unique behaviors and patterns across your Coder users +- Run a [study](https://getdx.com/studies/) or setup a [PlatformX](https://getdx.com/platformx/) event for deeper analysis + +## Related Resources + +- [DX Data Cloud Documentation](https://help.getdx.com/en/) +- [Coder CLI](../../reference/cli/users.md) +- [Coder API](../../reference/api/users.md) +- [PlatformX Integration](./platformx.md) diff --git a/docs/manifest.json b/docs/manifest.json index 2aa9cb0ead9ce..c01eb828ac3a1 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -697,6 +697,11 @@ "description": "Integrate Coder with DX PlatformX", "path": "./admin/integrations/platformx.md" }, + { + "title": "DX Data Cloud", + "description": "Tag Coder Users with DX Data Cloud", + "path": "./admin/integrations/dx-data-cloud.md" + }, { "title": "Hashicorp Vault", "description": "Integrate Coder with Hashicorp Vault", From 579c851285dd25480088865f1c93113af0b323f4 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 20 Jun 2025 22:18:07 +0100 Subject: [PATCH 3/5] chore: update template opt-in checkbox (#18481) Screenshot 2025-06-20 at 20 01 55 --- .../TemplateSettingsForm.tsx | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx index 1a02d50d06dcf..d6b56fd06e24f 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx @@ -10,6 +10,7 @@ import { } from "api/typesGenerated"; import { PremiumBadge } from "components/Badges/Badges"; import { Button } from "components/Button/Button"; +import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge"; import { FormFields, FormFooter, @@ -17,6 +18,7 @@ import { HorizontalForm, } from "components/Form/Form"; import { IconField } from "components/IconField/IconField"; +import { Link } from "components/Link/Link"; import { Spinner } from "components/Spinner/Spinner"; import { Stack } from "components/Stack/Stack"; import { @@ -25,6 +27,7 @@ import { } from "components/StackLabel/StackLabel"; import { type FormikTouched, useFormik } from "formik"; import type { FC } from "react"; +import { docs } from "utils/docs"; import { displayNameValidator, getFormHelpers, @@ -230,21 +233,37 @@ export const TemplateSettingsForm: FC = ({ size="small" id="use_classic_parameter_flow" name="use_classic_parameter_flow" - checked={form.values.use_classic_parameter_flow} - onChange={form.handleChange} + checked={!form.values.use_classic_parameter_flow} + onChange={(event) => + form.setFieldValue( + "use_classic_parameter_flow", + !event.currentTarget.checked, + ) + } disabled={false} /> } label={ - Use classic workspace creation form + + Enable dynamic parameters for workspace creation + + - - Show the original workspace creation form and workspace - parameters settings form without dynamic parameters or live - updates. Recommended if your provisioners aren't updated or - the dynamic form causes issues. - +
+ The new workspace form allows you to design your template + with new form types and identity-aware conditional + parameters. The form will only present options that are + compatible and available. +
+ + Learn more +
} From 0258f1d771188fceffa0172ba4a8f4c2a1143348 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Sat, 21 Jun 2025 02:36:14 +0500 Subject: [PATCH 4/5] docs: update Railway.com installation link (#18484) --- docs/install/other/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/install/other/index.md b/docs/install/other/index.md index f727e5c34bf55..1aa6d195bcdbb 100644 --- a/docs/install/other/index.md +++ b/docs/install/other/index.md @@ -1,6 +1,6 @@ # Alternate install methods -Coder has a number of alternate unofficial install methods. Contributions are +Coder has a number of alternate unofficial installation methods. Contributions are welcome! | Platform Name | Status | Documentation | @@ -9,7 +9,7 @@ welcome! | Terraform (GKE, AKS, LKE, DOKS, IBMCloud K8s, OVHCloud K8s, Scaleway K8s Kapsule) | Unofficial | [GitHub: coder-oss-terraform](https://github.com/ElliotG/coder-oss-tf) | | Fly.io | Unofficial | [Blog: Run Coder on Fly.io](https://coder.com/blog/remote-developer-environments-on-fly-io) | | Garden.io | Unofficial | [GitHub: garden-coder-example](https://github.com/garden-io/garden-coder-example) | -| Railway.app | Unofficial | [Blog: Run Coder on Railway.app](https://coder.com/blog/deploy-coder-on-railway-app) | +| Railway.com | Unofficial | [Run Coder on Railway.com](https://railway.com/deploy/coder) | | Heroku | Unofficial | [Docs: Deploy Coder on Heroku](https://github.com/coder/packages/blob/main/heroku/README.md) | | Render | Unofficial | [Docs: Deploy Coder on Render](https://github.com/coder/packages/blob/main/render/README.md) | | Snapcraft | Unofficial | [Get it from the Snap Store](https://snapcraft.io/coder) | From 0a483ea2b7ff99c878b6d3abc4783bda8f0dbf22 Mon Sep 17 00:00:00 2001 From: Asher Date: Fri, 20 Jun 2025 14:34:31 -0800 Subject: [PATCH 5/5] feat: add idle app status (#18415) "Idle" is more accurate than "complete" since: 1. AgentAPI only knows if the screen is active; it has no way of knowing if the task is complete. 2. The LLM might be done with its current prompt, but that does not mean the task is complete either (it likely needs refinement). The "complete" state will be reserved for future definition. Additionally, in the case where the screen goes idle but the LLM never reported a status update, we can get an idle icon without a message, and it looks kinda janky in the UI so if there is no message I display the state text. Closes https://github.com/coder/internal/issues/699 --- cli/exp_mcp.go | 4 +-- cli/exp_mcp_test.go | 4 +-- coderd/apidoc/docs.go | 2 ++ coderd/apidoc/swagger.json | 3 +- coderd/database/dump.sql | 3 +- .../000340_workspace_app_status_idle.down.sql | 15 ++++++++ .../000340_workspace_app_status_idle.up.sql | 1 + coderd/database/models.go | 5 ++- coderd/workspaceagents.go | 5 ++- codersdk/toolsdk/toolsdk.go | 6 ++-- codersdk/workspaceapps.go | 1 + docs/reference/api/builds.md | 2 ++ docs/reference/api/schemas.md | 1 + docs/reference/api/templates.md | 2 ++ site/src/api/typesGenerated.ts | 7 +++- site/src/modules/apps/AppStatusStateIcon.tsx | 5 +++ .../WorkspaceAppStatus.stories.tsx | 20 +++++++++++ .../WorkspaceAppStatus/WorkspaceAppStatus.tsx | 6 ++-- .../WorkspacePage/AppStatuses.stories.tsx | 34 +++++++++++++++++++ site/src/pages/WorkspacePage/AppStatuses.tsx | 5 +-- 20 files changed, 115 insertions(+), 16 deletions(-) create mode 100644 coderd/database/migrations/000340_workspace_app_status_idle.down.sql create mode 100644 coderd/database/migrations/000340_workspace_app_status_idle.up.sql diff --git a/cli/exp_mcp.go b/cli/exp_mcp.go index d487af5691bca..0a1c9fcbeaf87 100644 --- a/cli/exp_mcp.go +++ b/cli/exp_mcp.go @@ -585,10 +585,10 @@ func (s *mcpServer) startWatcher(ctx context.Context, inv *serpent.Invocation) { case event := <-eventsCh: switch ev := event.(type) { case agentapi.EventStatusChange: - // If the screen is stable, assume complete. + // If the screen is stable, report idle. state := codersdk.WorkspaceAppStatusStateWorking if ev.Status == agentapi.StatusStable { - state = codersdk.WorkspaceAppStatusStateComplete + state = codersdk.WorkspaceAppStatusStateIdle } err := s.queue.Push(taskReport{ state: state, diff --git a/cli/exp_mcp_test.go b/cli/exp_mcp_test.go index 08d6fbc4e2ce6..bcfafb0204874 100644 --- a/cli/exp_mcp_test.go +++ b/cli/exp_mcp_test.go @@ -900,7 +900,7 @@ func TestExpMcpReporter(t *testing.T) { { event: makeStatusEvent(agentapi.StatusStable), expected: &codersdk.WorkspaceAppStatus{ - State: codersdk.WorkspaceAppStatusStateComplete, + State: codersdk.WorkspaceAppStatusStateIdle, Message: "doing work", URI: "https://dev.coder.com", }, @@ -948,7 +948,7 @@ func TestExpMcpReporter(t *testing.T) { { event: makeStatusEvent(agentapi.StatusStable), expected: &codersdk.WorkspaceAppStatus{ - State: codersdk.WorkspaceAppStatusStateComplete, + State: codersdk.WorkspaceAppStatusStateIdle, Message: "oops", URI: "", }, diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 1d175333c1271..6844d166d8f1d 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -18081,11 +18081,13 @@ const docTemplate = `{ "type": "string", "enum": [ "working", + "idle", "complete", "failure" ], "x-enum-varnames": [ "WorkspaceAppStatusStateWorking", + "WorkspaceAppStatusStateIdle", "WorkspaceAppStatusStateComplete", "WorkspaceAppStatusStateFailure" ] diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 9d00a7ba34c30..35d39cfbe3839 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -16522,9 +16522,10 @@ }, "codersdk.WorkspaceAppStatusState": { "type": "string", - "enum": ["working", "complete", "failure"], + "enum": ["working", "idle", "complete", "failure"], "x-enum-varnames": [ "WorkspaceAppStatusStateWorking", + "WorkspaceAppStatusStateIdle", "WorkspaceAppStatusStateComplete", "WorkspaceAppStatusStateFailure" ] diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 2a94ef0fe7b4e..17b16abc5715c 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -324,7 +324,8 @@ CREATE TYPE workspace_app_open_in AS ENUM ( CREATE TYPE workspace_app_status_state AS ENUM ( 'working', 'complete', - 'failure' + 'failure', + 'idle' ); CREATE TYPE workspace_transition AS ENUM ( diff --git a/coderd/database/migrations/000340_workspace_app_status_idle.down.sql b/coderd/database/migrations/000340_workspace_app_status_idle.down.sql new file mode 100644 index 0000000000000..a5d2095b1cd4a --- /dev/null +++ b/coderd/database/migrations/000340_workspace_app_status_idle.down.sql @@ -0,0 +1,15 @@ +-- It is not possible to delete a value from an enum, so we have to recreate it. +CREATE TYPE old_workspace_app_status_state AS ENUM ('working', 'complete', 'failure'); + +-- Convert the new "idle" state into "complete". This means we lose some +-- information when downgrading, but this is necessary to swap to the old enum. +UPDATE workspace_app_statuses SET state = 'complete' WHERE state = 'idle'; + +-- Swap to the old enum. +ALTER TABLE workspace_app_statuses +ALTER COLUMN state TYPE old_workspace_app_status_state +USING (state::text::old_workspace_app_status_state); + +-- Drop the new enum and rename the old one to the final name. +DROP TYPE workspace_app_status_state; +ALTER TYPE old_workspace_app_status_state RENAME TO workspace_app_status_state; diff --git a/coderd/database/migrations/000340_workspace_app_status_idle.up.sql b/coderd/database/migrations/000340_workspace_app_status_idle.up.sql new file mode 100644 index 0000000000000..1630e3580f45c --- /dev/null +++ b/coderd/database/migrations/000340_workspace_app_status_idle.up.sql @@ -0,0 +1 @@ +ALTER TYPE workspace_app_status_state ADD VALUE IF NOT EXISTS 'idle'; diff --git a/coderd/database/models.go b/coderd/database/models.go index 6a571ffc1d0d4..ce65c4c559d65 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -2628,6 +2628,7 @@ const ( WorkspaceAppStatusStateWorking WorkspaceAppStatusState = "working" WorkspaceAppStatusStateComplete WorkspaceAppStatusState = "complete" WorkspaceAppStatusStateFailure WorkspaceAppStatusState = "failure" + WorkspaceAppStatusStateIdle WorkspaceAppStatusState = "idle" ) func (e *WorkspaceAppStatusState) Scan(src interface{}) error { @@ -2669,7 +2670,8 @@ func (e WorkspaceAppStatusState) Valid() bool { switch e { case WorkspaceAppStatusStateWorking, WorkspaceAppStatusStateComplete, - WorkspaceAppStatusStateFailure: + WorkspaceAppStatusStateFailure, + WorkspaceAppStatusStateIdle: return true } return false @@ -2680,6 +2682,7 @@ func AllWorkspaceAppStatusStateValues() []WorkspaceAppStatusState { WorkspaceAppStatusStateWorking, WorkspaceAppStatusStateComplete, WorkspaceAppStatusStateFailure, + WorkspaceAppStatusStateIdle, } } diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index ed3f554a89b75..8282eb9e7d01f 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -359,7 +359,10 @@ func (api *API) patchWorkspaceAgentAppStatus(rw http.ResponseWriter, r *http.Req } switch req.State { - case codersdk.WorkspaceAppStatusStateComplete, codersdk.WorkspaceAppStatusStateFailure, codersdk.WorkspaceAppStatusStateWorking: // valid states + case codersdk.WorkspaceAppStatusStateComplete, + codersdk.WorkspaceAppStatusStateFailure, + codersdk.WorkspaceAppStatusStateWorking, + codersdk.WorkspaceAppStatusStateIdle: // valid states default: httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid state provided.", diff --git a/codersdk/toolsdk/toolsdk.go b/codersdk/toolsdk/toolsdk.go index bb1649efa1993..3b992124005ac 100644 --- a/codersdk/toolsdk/toolsdk.go +++ b/codersdk/toolsdk/toolsdk.go @@ -191,7 +191,7 @@ Bad Tasks Use the "state" field to indicate your progress. Periodically report progress with state "working" to keep the user updated. It is not possible to send too many updates! -ONLY report a "complete" or "failure" state if you have FULLY completed the task. +ONLY report an "idle" or "failure" state if you have FULLY completed the task. `, Schema: aisdk.Schema{ Properties: map[string]any{ @@ -205,10 +205,10 @@ ONLY report a "complete" or "failure" state if you have FULLY completed the task }, "state": map[string]any{ "type": "string", - "description": "The state of your task. This can be one of the following: working, complete, or failure. Select the state that best represents your current progress.", + "description": "The state of your task. This can be one of the following: working, idle, or failure. Select the state that best represents your current progress.", "enum": []string{ string(codersdk.WorkspaceAppStatusStateWorking), - string(codersdk.WorkspaceAppStatusStateComplete), + string(codersdk.WorkspaceAppStatusStateIdle), string(codersdk.WorkspaceAppStatusStateFailure), }, }, diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index 556b3adb27b2e..6e95377bbaf42 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -19,6 +19,7 @@ type WorkspaceAppStatusState string const ( WorkspaceAppStatusStateWorking WorkspaceAppStatusState = "working" + WorkspaceAppStatusStateIdle WorkspaceAppStatusState = "idle" WorkspaceAppStatusStateComplete WorkspaceAppStatusState = "complete" WorkspaceAppStatusStateFailure WorkspaceAppStatusState = "failure" ) diff --git a/docs/reference/api/builds.md b/docs/reference/api/builds.md index 2a0e4b2ede1a0..c47b89d0bbba1 100644 --- a/docs/reference/api/builds.md +++ b/docs/reference/api/builds.md @@ -933,6 +933,7 @@ Status Code **200** | `sharing_level` | `organization` | | `sharing_level` | `public` | | `state` | `working` | +| `state` | `idle` | | `state` | `complete` | | `state` | `failure` | | `lifecycle_state` | `created` | @@ -1695,6 +1696,7 @@ Status Code **200** | `sharing_level` | `organization` | | `sharing_level` | `public` | | `state` | `working` | +| `state` | `idle` | | `state` | `complete` | | `state` | `failure` | | `lifecycle_state` | `created` | diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 8f548478e27a6..e0999f6bb3778 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -9686,6 +9686,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| | Value | |------------| | `working` | +| `idle` | | `complete` | | `failure` | diff --git a/docs/reference/api/templates.md b/docs/reference/api/templates.md index d695be4122951..85e865d8b4b37 100644 --- a/docs/reference/api/templates.md +++ b/docs/reference/api/templates.md @@ -2557,6 +2557,7 @@ Status Code **200** | `sharing_level` | `organization` | | `sharing_level` | `public` | | `state` | `working` | +| `state` | `idle` | | `state` | `complete` | | `state` | `failure` | | `lifecycle_state` | `created` | @@ -3233,6 +3234,7 @@ Status Code **200** | `sharing_level` | `organization` | | `sharing_level` | `public` | | `state` | `working` | +| `state` | `idle` | | `state` | `complete` | | `state` | `failure` | | `lifecycle_state` | `created` | diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 98338c24bb2d8..d536ac3a0fe5e 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -3622,11 +3622,16 @@ export interface WorkspaceAppStatus { } // From codersdk/workspaceapps.go -export type WorkspaceAppStatusState = "complete" | "failure" | "working"; +export type WorkspaceAppStatusState = + | "complete" + | "failure" + | "idle" + | "working"; export const WorkspaceAppStatusStates: WorkspaceAppStatusState[] = [ "complete", "failure", + "idle", "working", ]; diff --git a/site/src/modules/apps/AppStatusStateIcon.tsx b/site/src/modules/apps/AppStatusStateIcon.tsx index 829a8288235de..f713f49ed24b0 100644 --- a/site/src/modules/apps/AppStatusStateIcon.tsx +++ b/site/src/modules/apps/AppStatusStateIcon.tsx @@ -5,6 +5,7 @@ import { CircleAlertIcon, CircleCheckIcon, HourglassIcon, + SquareIcon, TriangleAlertIcon, } from "lucide-react"; import type { FC } from "react"; @@ -26,6 +27,10 @@ export const AppStatusStateIcon: FC = ({ const className = cn(["size-4 shrink-0", customClassName]); switch (state) { + case "idle": + return ( + + ); case "complete": return ( diff --git a/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx b/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx index d95444e658d64..0e229467b994b 100644 --- a/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx +++ b/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx @@ -39,6 +39,26 @@ export const Working: Story = { }, }; +export const Idle: Story = { + args: { + status: { + ...MockWorkspaceAppStatus, + state: "idle", + message: "Done for now", + }, + }, +}; + +export const NoMessage: Story = { + args: { + status: { + ...MockWorkspaceAppStatus, + state: "idle", + message: "", + }, + }, +}; + export const LongMessage: Story = { args: { status: { diff --git a/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.tsx b/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.tsx index 0b999f54402a8..587ae9f5b062f 100644 --- a/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.tsx +++ b/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.tsx @@ -5,6 +5,7 @@ import { TooltipProvider, TooltipTrigger, } from "components/Tooltip/Tooltip"; +import capitalize from "lodash/capitalize"; import { AppStatusStateIcon } from "modules/apps/AppStatusStateIcon"; import { cn } from "utils/cn"; @@ -25,6 +26,7 @@ export const WorkspaceAppStatus = ({ ); } + const message = status.message || capitalize(status.state); return (
@@ -40,11 +42,11 @@ export const WorkspaceAppStatus = ({ })} /> - {status.message} + {message}
- {status.message} + {message} diff --git a/site/src/pages/WorkspacePage/AppStatuses.stories.tsx b/site/src/pages/WorkspacePage/AppStatuses.stories.tsx index 90be0f194fef3..c7ec5eb56f417 100644 --- a/site/src/pages/WorkspacePage/AppStatuses.stories.tsx +++ b/site/src/pages/WorkspacePage/AppStatuses.stories.tsx @@ -48,6 +48,40 @@ export const WorkingState: Story = { }, }; +export const IdleState: Story = { + args: { + agent: mockAgent([ + { + ...MockWorkspaceAppStatus, + id: "status-8", + icon: "", + message: "Done for now", + created_at: createTimestamp(5, 20), + uri: "", + state: "idle" as const, + }, + ...MockWorkspaceAppStatuses, + ]), + }, +}; + +export const NoMessage: Story = { + args: { + agent: mockAgent([ + { + ...MockWorkspaceAppStatus, + id: "status-8", + icon: "", + message: "", + created_at: createTimestamp(5, 20), + uri: "", + state: "idle" as const, + }, + ...MockWorkspaceAppStatuses, + ]), + }, +}; + export const LongStatusText: Story = { args: { agent: mockAgent([ diff --git a/site/src/pages/WorkspacePage/AppStatuses.tsx b/site/src/pages/WorkspacePage/AppStatuses.tsx index 35d4db46c3ac9..95e3f9c95a472 100644 --- a/site/src/pages/WorkspacePage/AppStatuses.tsx +++ b/site/src/pages/WorkspacePage/AppStatuses.tsx @@ -12,6 +12,7 @@ import { TooltipProvider, TooltipTrigger, } from "components/Tooltip/Tooltip"; +import capitalize from "lodash/capitalize"; import { timeFrom } from "utils/time"; import { @@ -77,7 +78,7 @@ export const AppStatuses: FC = ({
- {latestStatus.message} + {latestStatus.message || capitalize(latestStatus.state)}
@@ -160,7 +161,7 @@ export const AppStatuses: FC = ({ latest={false} className="size-icon-xs w-[18px]" /> - {status.message} + {status.message || capitalize(status.state)} {formattedTimestamp}