mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: extend workspace build reasons to track connection types (#18827)
This PR introduces new build reason values to identify what type of connection triggered a workspace build, helping to troubleshoot workspace-related issues. ## Database Migration Added migration 000349_extend_workspace_build_reason.up.sql that extends the build_reason enum with new values: ``` dashboard, cli, ssh_connection, vscode_connection, jetbrains_connection ``` ## Implementation The build reason is specified through the API when creating new workspace builds: - Dashboard: Automatically sets reason to `dashboard` when users start workspaces via the web interface - CLI `start` command: Sets reason to `cli` when workspaces are started via the command line - CLI `ssh` command: Sets reason to ssh_connection when workspaces are started due to SSH connections - VS Code connections: Will be set to `vscode_connection` by the VS Code extension through CLI hidden flag (https://github.com/coder/vscode-coder/pull/550) - JetBrains connections: Will be set to `jetbrains_connection` by the Jetbrains Toolbox (https://github.com/coder/coder-jetbrains-toolbox/pull/150) and Jetbrains Gateway extension (https://github.com/coder/jetbrains-coder/pull/561) ## UI Changes: * Tooltip with reason in Build history <img width="309" height="457" alt="image" src="https://github.com/user-attachments/assets/bde8440b-bf3b-49a1-a244-ed7e8eb9763c" /> * Reason in Audit Logs Row tooltip <img width="906" height="237" alt="image" src="https://github.com/user-attachments/assets/ebbb62c7-cf07-4398-afbf-323c83fb6426" /> <img width="909" height="188" alt="image" src="https://github.com/user-attachments/assets/1ddbab07-44bf-4dee-8867-b4e2cd56ae96" />
This commit is contained in:
+15
-1
@@ -145,9 +145,11 @@ func parseParameterMapFile(parameterFile string) (map[string]string, error) {
|
||||
return parameterMap, nil
|
||||
}
|
||||
|
||||
// buildFlags contains options relating to troubleshooting provisioner jobs.
|
||||
// buildFlags contains options relating to troubleshooting provisioner jobs
|
||||
// and setting the reason for the workspace build.
|
||||
type buildFlags struct {
|
||||
provisionerLogDebug bool
|
||||
reason string
|
||||
}
|
||||
|
||||
func (bf *buildFlags) cliOptions() []serpent.Option {
|
||||
@@ -160,5 +162,17 @@ This is useful for troubleshooting build issues.`,
|
||||
Value: serpent.BoolOf(&bf.provisionerLogDebug),
|
||||
Hidden: true,
|
||||
},
|
||||
{
|
||||
Flag: "reason",
|
||||
Description: `Sets the reason for the workspace build (cli, vscode_connection, jetbrains_connection).`,
|
||||
Value: serpent.EnumOf(
|
||||
&bf.reason,
|
||||
string(codersdk.BuildReasonCLI),
|
||||
string(codersdk.BuildReasonVSCodeConnection),
|
||||
string(codersdk.BuildReasonJetbrainsConnection),
|
||||
),
|
||||
Default: string(codersdk.BuildReasonCLI),
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -873,7 +873,9 @@ func getWorkspaceAndAgent(ctx context.Context, inv *serpent.Invocation, client *
|
||||
// It's possible for a workspace build to fail due to the template requiring starting
|
||||
// workspaces with the active version.
|
||||
_, _ = fmt.Fprintf(inv.Stderr, "Workspace was stopped, starting workspace to allow connecting to %q...\n", workspace.Name)
|
||||
_, err = startWorkspace(inv, client, workspace, workspaceParameterFlags{}, buildFlags{}, WorkspaceStart)
|
||||
_, err = startWorkspace(inv, client, workspace, workspaceParameterFlags{}, buildFlags{
|
||||
reason: string(codersdk.BuildReasonSSHConnection),
|
||||
}, WorkspaceStart)
|
||||
if cerr, ok := codersdk.AsError(err); ok {
|
||||
switch cerr.StatusCode() {
|
||||
case http.StatusConflict:
|
||||
|
||||
@@ -169,6 +169,9 @@ func buildWorkspaceStartRequest(inv *serpent.Invocation, client *codersdk.Client
|
||||
if buildFlags.provisionerLogDebug {
|
||||
wbr.LogLevel = codersdk.ProvisionerLogLevelDebug
|
||||
}
|
||||
if buildFlags.reason != "" {
|
||||
wbr.Reason = codersdk.CreateWorkspaceBuildReason(buildFlags.reason)
|
||||
}
|
||||
|
||||
return wbr, nil
|
||||
}
|
||||
|
||||
@@ -477,3 +477,39 @@ func TestStart_NoWait(t *testing.T) {
|
||||
pty.ExpectMatch("workspace has been started in no-wait mode")
|
||||
_ = testutil.TryReceive(ctx, t, doneChan)
|
||||
}
|
||||
|
||||
func TestStart_WithReason(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
// Prepare user, template, workspace
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||
version1 := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version1.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version1.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, member, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
// Stop the workspace
|
||||
build := coderdtest.CreateWorkspaceBuild(t, member, workspace, database.WorkspaceTransitionStop)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, build.ID)
|
||||
|
||||
// Start the workspace with reason
|
||||
inv, root := clitest.New(t, "start", workspace.Name, "--reason", "cli")
|
||||
clitest.SetupConfig(t, member, root)
|
||||
doneChan := make(chan struct{})
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
go func() {
|
||||
defer close(doneChan)
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
pty.ExpectMatch("workspace has been started")
|
||||
_ = testutil.TryReceive(ctx, t, doneChan)
|
||||
|
||||
workspace = coderdtest.MustWorkspace(t, member, workspace.ID)
|
||||
require.Equal(t, codersdk.BuildReasonCLI, workspace.LatestBuild.Reason)
|
||||
}
|
||||
|
||||
Generated
+44
-2
@@ -11448,13 +11448,23 @@ const docTemplate = `{
|
||||
"initiator",
|
||||
"autostart",
|
||||
"autostop",
|
||||
"dormancy"
|
||||
"dormancy",
|
||||
"dashboard",
|
||||
"cli",
|
||||
"ssh_connection",
|
||||
"vscode_connection",
|
||||
"jetbrains_connection"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"BuildReasonInitiator",
|
||||
"BuildReasonAutostart",
|
||||
"BuildReasonAutostop",
|
||||
"BuildReasonDormancy"
|
||||
"BuildReasonDormancy",
|
||||
"BuildReasonDashboard",
|
||||
"BuildReasonCLI",
|
||||
"BuildReasonSSHConnection",
|
||||
"BuildReasonVSCodeConnection",
|
||||
"BuildReasonJetbrainsConnection"
|
||||
]
|
||||
},
|
||||
"codersdk.ChangePasswordWithOneTimePasscodeRequest": {
|
||||
@@ -12070,6 +12080,23 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateWorkspaceBuildReason": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"dashboard",
|
||||
"cli",
|
||||
"ssh_connection",
|
||||
"vscode_connection",
|
||||
"jetbrains_connection"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"CreateWorkspaceBuildReasonDashboard",
|
||||
"CreateWorkspaceBuildReasonCLI",
|
||||
"CreateWorkspaceBuildReasonSSHConnection",
|
||||
"CreateWorkspaceBuildReasonVSCodeConnection",
|
||||
"CreateWorkspaceBuildReasonJetbrainsConnection"
|
||||
]
|
||||
},
|
||||
"codersdk.CreateWorkspaceBuildRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -12094,6 +12121,21 @@ const docTemplate = `{
|
||||
"description": "Orphan may be set for the Destroy transition.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"reason": {
|
||||
"description": "Reason sets the reason for the workspace build.",
|
||||
"enum": [
|
||||
"dashboard",
|
||||
"cli",
|
||||
"ssh_connection",
|
||||
"vscode_connection",
|
||||
"jetbrains_connection"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.CreateWorkspaceBuildReason"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rich_parameter_values": {
|
||||
"description": "ParameterValues are optional. It will write params to the 'workspace' scope.\nThis will overwrite any existing parameters with the same name.\nThis will not delete old params not included in this list.",
|
||||
"type": "array",
|
||||
|
||||
Generated
+49
-2
@@ -10179,12 +10179,27 @@
|
||||
},
|
||||
"codersdk.BuildReason": {
|
||||
"type": "string",
|
||||
"enum": ["initiator", "autostart", "autostop", "dormancy"],
|
||||
"enum": [
|
||||
"initiator",
|
||||
"autostart",
|
||||
"autostop",
|
||||
"dormancy",
|
||||
"dashboard",
|
||||
"cli",
|
||||
"ssh_connection",
|
||||
"vscode_connection",
|
||||
"jetbrains_connection"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"BuildReasonInitiator",
|
||||
"BuildReasonAutostart",
|
||||
"BuildReasonAutostop",
|
||||
"BuildReasonDormancy"
|
||||
"BuildReasonDormancy",
|
||||
"BuildReasonDashboard",
|
||||
"BuildReasonCLI",
|
||||
"BuildReasonSSHConnection",
|
||||
"BuildReasonVSCodeConnection",
|
||||
"BuildReasonJetbrainsConnection"
|
||||
]
|
||||
},
|
||||
"codersdk.ChangePasswordWithOneTimePasscodeRequest": {
|
||||
@@ -10758,6 +10773,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateWorkspaceBuildReason": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"dashboard",
|
||||
"cli",
|
||||
"ssh_connection",
|
||||
"vscode_connection",
|
||||
"jetbrains_connection"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"CreateWorkspaceBuildReasonDashboard",
|
||||
"CreateWorkspaceBuildReasonCLI",
|
||||
"CreateWorkspaceBuildReasonSSHConnection",
|
||||
"CreateWorkspaceBuildReasonVSCodeConnection",
|
||||
"CreateWorkspaceBuildReasonJetbrainsConnection"
|
||||
]
|
||||
},
|
||||
"codersdk.CreateWorkspaceBuildRequest": {
|
||||
"type": "object",
|
||||
"required": ["transition"],
|
||||
@@ -10778,6 +10810,21 @@
|
||||
"description": "Orphan may be set for the Destroy transition.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"reason": {
|
||||
"description": "Reason sets the reason for the workspace build.",
|
||||
"enum": [
|
||||
"dashboard",
|
||||
"cli",
|
||||
"ssh_connection",
|
||||
"vscode_connection",
|
||||
"jetbrains_connection"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.CreateWorkspaceBuildReason"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rich_parameter_values": {
|
||||
"description": "ParameterValues are optional. It will write params to the 'workspace' scope.\nThis will overwrite any existing parameters with the same name.\nThis will not delete old params not included in this list.",
|
||||
"type": "array",
|
||||
|
||||
Generated
+6
-1
@@ -51,7 +51,12 @@ CREATE TYPE build_reason AS ENUM (
|
||||
'autostop',
|
||||
'dormancy',
|
||||
'failedstop',
|
||||
'autodelete'
|
||||
'autodelete',
|
||||
'dashboard',
|
||||
'cli',
|
||||
'ssh_connection',
|
||||
'vscode_connection',
|
||||
'jetbrains_connection'
|
||||
);
|
||||
|
||||
CREATE TYPE connection_status AS ENUM (
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-- It's not possible to delete enum values.
|
||||
@@ -0,0 +1,5 @@
|
||||
ALTER TYPE build_reason ADD VALUE IF NOT EXISTS 'dashboard';
|
||||
ALTER TYPE build_reason ADD VALUE IF NOT EXISTS 'cli';
|
||||
ALTER TYPE build_reason ADD VALUE IF NOT EXISTS 'ssh_connection';
|
||||
ALTER TYPE build_reason ADD VALUE IF NOT EXISTS 'vscode_connection';
|
||||
ALTER TYPE build_reason ADD VALUE IF NOT EXISTS 'jetbrains_connection';
|
||||
@@ -349,12 +349,17 @@ func AllAutomaticUpdatesValues() []AutomaticUpdates {
|
||||
type BuildReason string
|
||||
|
||||
const (
|
||||
BuildReasonInitiator BuildReason = "initiator"
|
||||
BuildReasonAutostart BuildReason = "autostart"
|
||||
BuildReasonAutostop BuildReason = "autostop"
|
||||
BuildReasonDormancy BuildReason = "dormancy"
|
||||
BuildReasonFailedstop BuildReason = "failedstop"
|
||||
BuildReasonAutodelete BuildReason = "autodelete"
|
||||
BuildReasonInitiator BuildReason = "initiator"
|
||||
BuildReasonAutostart BuildReason = "autostart"
|
||||
BuildReasonAutostop BuildReason = "autostop"
|
||||
BuildReasonDormancy BuildReason = "dormancy"
|
||||
BuildReasonFailedstop BuildReason = "failedstop"
|
||||
BuildReasonAutodelete BuildReason = "autodelete"
|
||||
BuildReasonDashboard BuildReason = "dashboard"
|
||||
BuildReasonCli BuildReason = "cli"
|
||||
BuildReasonSshConnection BuildReason = "ssh_connection"
|
||||
BuildReasonVscodeConnection BuildReason = "vscode_connection"
|
||||
BuildReasonJetbrainsConnection BuildReason = "jetbrains_connection"
|
||||
)
|
||||
|
||||
func (e *BuildReason) Scan(src interface{}) error {
|
||||
@@ -399,7 +404,12 @@ func (e BuildReason) Valid() bool {
|
||||
BuildReasonAutostop,
|
||||
BuildReasonDormancy,
|
||||
BuildReasonFailedstop,
|
||||
BuildReasonAutodelete:
|
||||
BuildReasonAutodelete,
|
||||
BuildReasonDashboard,
|
||||
BuildReasonCli,
|
||||
BuildReasonSshConnection,
|
||||
BuildReasonVscodeConnection,
|
||||
BuildReasonJetbrainsConnection:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -413,6 +423,11 @@ func AllBuildReasonValues() []BuildReason {
|
||||
BuildReasonDormancy,
|
||||
BuildReasonFailedstop,
|
||||
BuildReasonAutodelete,
|
||||
BuildReasonDashboard,
|
||||
BuildReasonCli,
|
||||
BuildReasonSshConnection,
|
||||
BuildReasonVscodeConnection,
|
||||
BuildReasonJetbrainsConnection,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -329,13 +329,15 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ
|
||||
func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
apiKey := httpmw.APIKey(r)
|
||||
|
||||
workspace := httpmw.WorkspaceParam(r)
|
||||
var createBuild codersdk.CreateWorkspaceBuildRequest
|
||||
if !httpapi.Read(ctx, rw, r, &createBuild) {
|
||||
return
|
||||
}
|
||||
|
||||
builder := wsbuilder.New(workspace, database.WorkspaceTransition(createBuild.Transition), *api.BuildUsageChecker.Load()).
|
||||
transition := database.WorkspaceTransition(createBuild.Transition)
|
||||
builder := wsbuilder.New(workspace, transition, *api.BuildUsageChecker.Load()).
|
||||
Initiator(apiKey.UserID).
|
||||
RichParameterValues(createBuild.RichParameterValues).
|
||||
LogLevel(string(createBuild.LogLevel)).
|
||||
@@ -343,6 +345,10 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||
Experiments(api.Experiments).
|
||||
TemplateVersionPresetID(createBuild.TemplateVersionPresetID)
|
||||
|
||||
if transition == database.WorkspaceTransitionStart && createBuild.Reason != "" {
|
||||
builder = builder.Reason(database.BuildReason(createBuild.Reason))
|
||||
}
|
||||
|
||||
var (
|
||||
previousWorkspaceBuild database.WorkspaceBuild
|
||||
workspaceBuild *database.WorkspaceBuild
|
||||
|
||||
@@ -1808,6 +1808,30 @@ func TestPostWorkspaceBuild(t *testing.T) {
|
||||
assert.True(t, build.MatchedProvisioners.MostRecentlySeen.Valid)
|
||||
}
|
||||
})
|
||||
t.Run("WithReason", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client, closeDaemon := coderdtest.NewWithProvisionerCloser(t, &coderdtest.Options{
|
||||
IncludeProvisionerDaemon: true,
|
||||
})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
_ = closeDaemon.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
build, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||
TemplateVersionID: template.ActiveVersionID,
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
Reason: codersdk.CreateWorkspaceBuildReasonDashboard,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, codersdk.BuildReasonDashboard, build.Reason)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspaceBuildTimings(t *testing.T) {
|
||||
|
||||
@@ -49,6 +49,16 @@ const (
|
||||
// BuildReasonDormancy "dormancy" is used when a build to stop a workspace is triggered due to inactivity (dormancy).
|
||||
// The initiator id/username in this case is the workspace owner and can be ignored.
|
||||
BuildReasonDormancy BuildReason = "dormancy"
|
||||
// BuildReasonDashboard "dashboard" is used when a build to start a workspace is triggered by the dashboard.
|
||||
BuildReasonDashboard BuildReason = "dashboard"
|
||||
// BuildReasonCLI "cli" is used when a build to start a workspace is triggered by the CLI.
|
||||
BuildReasonCLI BuildReason = "cli"
|
||||
// BuildReasonSSHConnection "ssh_connection" is used when a build to start a workspace is triggered by an SSH connection.
|
||||
BuildReasonSSHConnection BuildReason = "ssh_connection"
|
||||
// BuildReasonVSCodeConnection "vscode_connection" is used when a build to start a workspace is triggered by a VS Code connection.
|
||||
BuildReasonVSCodeConnection BuildReason = "vscode_connection"
|
||||
// BuildReasonJetbrainsConnection "jetbrains_connection" is used when a build to start a workspace is triggered by a JetBrains connection.
|
||||
BuildReasonJetbrainsConnection BuildReason = "jetbrains_connection"
|
||||
)
|
||||
|
||||
// WorkspaceBuild is an at-point representation of a workspace state.
|
||||
|
||||
@@ -99,6 +99,16 @@ const (
|
||||
ProvisionerLogLevelDebug ProvisionerLogLevel = "debug"
|
||||
)
|
||||
|
||||
type CreateWorkspaceBuildReason string
|
||||
|
||||
const (
|
||||
CreateWorkspaceBuildReasonDashboard CreateWorkspaceBuildReason = "dashboard"
|
||||
CreateWorkspaceBuildReasonCLI CreateWorkspaceBuildReason = "cli"
|
||||
CreateWorkspaceBuildReasonSSHConnection CreateWorkspaceBuildReason = "ssh_connection"
|
||||
CreateWorkspaceBuildReasonVSCodeConnection CreateWorkspaceBuildReason = "vscode_connection"
|
||||
CreateWorkspaceBuildReasonJetbrainsConnection CreateWorkspaceBuildReason = "jetbrains_connection"
|
||||
)
|
||||
|
||||
// CreateWorkspaceBuildRequest provides options to update the latest workspace build.
|
||||
type CreateWorkspaceBuildRequest struct {
|
||||
TemplateVersionID uuid.UUID `json:"template_version_id,omitempty" format:"uuid"`
|
||||
@@ -116,6 +126,8 @@ type CreateWorkspaceBuildRequest struct {
|
||||
LogLevel ProvisionerLogLevel `json:"log_level,omitempty" validate:"omitempty,oneof=debug"`
|
||||
// TemplateVersionPresetID is the ID of the template version preset to use for the build.
|
||||
TemplateVersionPresetID uuid.UUID `json:"template_version_preset_id,omitempty" format:"uuid"`
|
||||
// Reason sets the reason for the workspace build.
|
||||
Reason CreateWorkspaceBuildReason `json:"reason,omitempty" validate:"omitempty,oneof=dashboard cli ssh_connection vscode_connection jetbrains_connection"`
|
||||
}
|
||||
|
||||
type WorkspaceOptions struct {
|
||||
|
||||
Generated
+1
@@ -1762,6 +1762,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
|
||||
"dry_run": true,
|
||||
"log_level": "debug",
|
||||
"orphan": true,
|
||||
"reason": "dashboard",
|
||||
"rich_parameter_values": [
|
||||
{
|
||||
"name": "string",
|
||||
|
||||
Generated
+42
-12
@@ -1044,12 +1044,17 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value |
|
||||
|-------------|
|
||||
| `initiator` |
|
||||
| `autostart` |
|
||||
| `autostop` |
|
||||
| `dormancy` |
|
||||
| Value |
|
||||
|------------------------|
|
||||
| `initiator` |
|
||||
| `autostart` |
|
||||
| `autostop` |
|
||||
| `dormancy` |
|
||||
| `dashboard` |
|
||||
| `cli` |
|
||||
| `ssh_connection` |
|
||||
| `vscode_connection` |
|
||||
| `jetbrains_connection` |
|
||||
|
||||
## codersdk.ChangePasswordWithOneTimePasscodeRequest
|
||||
|
||||
@@ -1689,6 +1694,24 @@ This is required on creation to enable a user-flow of validating a template work
|
||||
| `user_status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | User status defaults to UserStatusDormant. |
|
||||
| `username` | string | true | | |
|
||||
|
||||
## codersdk.CreateWorkspaceBuildReason
|
||||
|
||||
```json
|
||||
"dashboard"
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value |
|
||||
|------------------------|
|
||||
| `dashboard` |
|
||||
| `cli` |
|
||||
| `ssh_connection` |
|
||||
| `vscode_connection` |
|
||||
| `jetbrains_connection` |
|
||||
|
||||
## codersdk.CreateWorkspaceBuildRequest
|
||||
|
||||
```json
|
||||
@@ -1696,6 +1719,7 @@ This is required on creation to enable a user-flow of validating a template work
|
||||
"dry_run": true,
|
||||
"log_level": "debug",
|
||||
"orphan": true,
|
||||
"reason": "dashboard",
|
||||
"rich_parameter_values": [
|
||||
{
|
||||
"name": "string",
|
||||
@@ -1718,6 +1742,7 @@ This is required on creation to enable a user-flow of validating a template work
|
||||
| `dry_run` | boolean | false | | |
|
||||
| `log_level` | [codersdk.ProvisionerLogLevel](#codersdkprovisionerloglevel) | false | | Log level changes the default logging verbosity of a provider ("info" if empty). |
|
||||
| `orphan` | boolean | false | | Orphan may be set for the Destroy transition. |
|
||||
| `reason` | [codersdk.CreateWorkspaceBuildReason](#codersdkcreateworkspacebuildreason) | false | | Reason sets the reason for the workspace build. |
|
||||
| `rich_parameter_values` | array of [codersdk.WorkspaceBuildParameter](#codersdkworkspacebuildparameter) | false | | Rich parameter values are optional. It will write params to the 'workspace' scope. This will overwrite any existing parameters with the same name. This will not delete old params not included in this list. |
|
||||
| `state` | array of integer | false | | |
|
||||
| `template_version_id` | string | false | | |
|
||||
@@ -1726,12 +1751,17 @@ This is required on creation to enable a user-flow of validating a template work
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
|--------------|----------|
|
||||
| `log_level` | `debug` |
|
||||
| `transition` | `start` |
|
||||
| `transition` | `stop` |
|
||||
| `transition` | `delete` |
|
||||
| Property | Value |
|
||||
|--------------|------------------------|
|
||||
| `log_level` | `debug` |
|
||||
| `reason` | `dashboard` |
|
||||
| `reason` | `cli` |
|
||||
| `reason` | `ssh_connection` |
|
||||
| `reason` | `vscode_connection` |
|
||||
| `reason` | `jetbrains_connection` |
|
||||
| `transition` | `start` |
|
||||
| `transition` | `stop` |
|
||||
| `transition` | `delete` |
|
||||
|
||||
## codersdk.CreateWorkspaceProxyRequest
|
||||
|
||||
|
||||
@@ -1272,6 +1272,7 @@ class ApiMethods {
|
||||
template_version_id: templateVersionId,
|
||||
log_level: logLevel,
|
||||
rich_parameter_values: buildParameters,
|
||||
reason: "dashboard",
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Generated
+32
-1
@@ -275,13 +275,27 @@ export interface BuildInfoResponse {
|
||||
}
|
||||
|
||||
// From codersdk/workspacebuilds.go
|
||||
export type BuildReason = "autostart" | "autostop" | "dormancy" | "initiator";
|
||||
export type BuildReason =
|
||||
| "autostart"
|
||||
| "autostop"
|
||||
| "cli"
|
||||
| "dashboard"
|
||||
| "dormancy"
|
||||
| "initiator"
|
||||
| "jetbrains_connection"
|
||||
| "ssh_connection"
|
||||
| "vscode_connection";
|
||||
|
||||
export const BuildReasons: BuildReason[] = [
|
||||
"autostart",
|
||||
"autostop",
|
||||
"cli",
|
||||
"dashboard",
|
||||
"dormancy",
|
||||
"initiator",
|
||||
"jetbrains_connection",
|
||||
"ssh_connection",
|
||||
"vscode_connection",
|
||||
];
|
||||
|
||||
// From codersdk/client.go
|
||||
@@ -530,6 +544,22 @@ export interface CreateUserRequestWithOrgs {
|
||||
readonly organization_ids: readonly string[];
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go
|
||||
export type CreateWorkspaceBuildReason =
|
||||
| "cli"
|
||||
| "dashboard"
|
||||
| "jetbrains_connection"
|
||||
| "ssh_connection"
|
||||
| "vscode_connection";
|
||||
|
||||
export const CreateWorkspaceBuildReasons: CreateWorkspaceBuildReason[] = [
|
||||
"cli",
|
||||
"dashboard",
|
||||
"jetbrains_connection",
|
||||
"ssh_connection",
|
||||
"vscode_connection",
|
||||
];
|
||||
|
||||
// From codersdk/workspaces.go
|
||||
export interface CreateWorkspaceBuildRequest {
|
||||
readonly template_version_id?: string;
|
||||
@@ -540,6 +570,7 @@ export interface CreateWorkspaceBuildRequest {
|
||||
readonly rich_parameter_values?: readonly WorkspaceBuildParameter[];
|
||||
readonly log_level?: ProvisionerLogLevel;
|
||||
readonly template_version_preset_id?: string;
|
||||
readonly reason?: CreateWorkspaceBuildReason;
|
||||
}
|
||||
|
||||
// From codersdk/workspaceproxy.go
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import Skeleton from "@mui/material/Skeleton";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { WorkspaceBuild } from "api/typesGenerated";
|
||||
import { BuildIcon } from "components/BuildIcon/BuildIcon";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
import { createDayString } from "utils/createDayString";
|
||||
import {
|
||||
buildReasonLabels,
|
||||
getDisplayWorkspaceBuildInitiatedBy,
|
||||
getDisplayWorkspaceBuildStatus,
|
||||
systemBuildReasons,
|
||||
} from "utils/workspace";
|
||||
|
||||
export const WorkspaceBuildData = ({ build }: { build: WorkspaceBuild }) => {
|
||||
@@ -29,6 +33,9 @@ export const WorkspaceBuildData = ({ build }: { build: WorkspaceBuild }) => {
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<span css={{ textTransform: "capitalize" }}>{build.transition}</span>{" "}
|
||||
@@ -36,6 +43,17 @@ export const WorkspaceBuildData = ({ build }: { build: WorkspaceBuild }) => {
|
||||
<span css={{ fontWeight: 500 }}>
|
||||
{getDisplayWorkspaceBuildInitiatedBy(build)}
|
||||
</span>
|
||||
{!systemBuildReasons.includes(build.reason) &&
|
||||
build.transition === "start" && (
|
||||
<Tooltip title={buildReasonLabels[build.reason]}>
|
||||
<InfoIcon
|
||||
css={(theme) => ({
|
||||
color: theme.palette.info.light,
|
||||
})}
|
||||
className="size-icon-xs -mt-px"
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
css={{
|
||||
|
||||
@@ -2,6 +2,7 @@ import Link from "@mui/material/Link";
|
||||
import type { AuditLog } from "api/typesGenerated";
|
||||
import { type FC, useMemo } from "react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { systemBuildReasons } from "utils/workspace";
|
||||
|
||||
interface BuildAuditDescriptionProps {
|
||||
auditLog: AuditLog;
|
||||
@@ -14,7 +15,7 @@ export const BuildAuditDescription: FC<BuildAuditDescriptionProps> = ({
|
||||
// workspaces can be started/stopped/deleted by a user, or kicked off automatically by Coder
|
||||
const user =
|
||||
auditLog.additional_fields?.build_reason &&
|
||||
auditLog.additional_fields?.build_reason !== "initiator"
|
||||
systemBuildReasons.includes(auditLog.additional_fields?.build_reason)
|
||||
? "Coder automatically"
|
||||
: auditLog.user
|
||||
? auditLog.user.username.trim()
|
||||
|
||||
@@ -3,7 +3,7 @@ import Collapse from "@mui/material/Collapse";
|
||||
import Link from "@mui/material/Link";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { AuditLog } from "api/typesGenerated";
|
||||
import type { AuditLog, BuildReason } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
@@ -14,6 +14,7 @@ import { NetworkIcon } from "lucide-react";
|
||||
import { type FC, useState } from "react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import userAgentParser from "ua-parser-js";
|
||||
import { buildReasonLabels } from "utils/workspace";
|
||||
import { AuditLogDescription } from "./AuditLogDescription/AuditLogDescription";
|
||||
import { AuditLogDiff } from "./AuditLogDiff/AuditLogDiff";
|
||||
import {
|
||||
@@ -166,12 +167,20 @@ export const AuditLogRow: FC<AuditLogRowProps> = ({
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{auditLog.additional_fields?.reason && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>Reason:</h4>
|
||||
<div>{auditLog.additional_fields?.reason}</div>
|
||||
</div>
|
||||
)}
|
||||
{auditLog.additional_fields?.build_reason &&
|
||||
auditLog.action === "start" && (
|
||||
<div>
|
||||
<h4 css={styles.auditLogInfoHeader}>Reason:</h4>
|
||||
<div>
|
||||
{
|
||||
buildReasonLabels[
|
||||
auditLog.additional_fields
|
||||
.build_reason as BuildReason
|
||||
]
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
@@ -203,6 +212,20 @@ export const AuditLogRow: FC<AuditLogRowProps> = ({
|
||||
</strong>
|
||||
</span>
|
||||
)}
|
||||
{auditLog.additional_fields?.build_reason &&
|
||||
auditLog.action === "start" && (
|
||||
<span css={styles.auditLogInfo}>
|
||||
<span>Reason: </span>
|
||||
<strong>
|
||||
{
|
||||
buildReasonLabels[
|
||||
auditLog.additional_fields
|
||||
.build_reason as BuildReason
|
||||
]
|
||||
}
|
||||
</strong>
|
||||
</span>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
+1
@@ -67,6 +67,7 @@ test("Submit the workspace settings page successfully", async () => {
|
||||
// Assert that the API calls were made with the correct data
|
||||
await waitFor(() => {
|
||||
expect(postWorkspaceBuildSpy).toHaveBeenCalledWith(MockWorkspace.id, {
|
||||
reason: "dashboard",
|
||||
transition: "start",
|
||||
rich_parameter_values: [
|
||||
{ name: MockTemplateVersionParameter1.name, value: "new-value" },
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ const WorkspaceParametersPage: FC = () => {
|
||||
API.postWorkspaceBuild(workspace.id, {
|
||||
transition: "start",
|
||||
rich_parameter_values: buildParameters,
|
||||
reason: "dashboard",
|
||||
}),
|
||||
onSuccess: () => {
|
||||
navigate(`/${workspace.owner_name}/${workspace.name}`);
|
||||
|
||||
+1
@@ -149,6 +149,7 @@ const WorkspaceParametersPageExperimental: FC = () => {
|
||||
transition: "start",
|
||||
template_version_id: templateVersionId,
|
||||
rich_parameter_values: buildParameters,
|
||||
reason: "dashboard",
|
||||
}),
|
||||
onSuccess: () => {
|
||||
navigate(`/@${workspace.owner_name}/${workspace.name}`);
|
||||
|
||||
@@ -78,6 +78,11 @@ export const getDisplayWorkspaceBuildInitiatedBy = (
|
||||
): string | undefined => {
|
||||
switch (build.reason) {
|
||||
case "initiator":
|
||||
case "dashboard":
|
||||
case "cli":
|
||||
case "ssh_connection":
|
||||
case "vscode_connection":
|
||||
case "jetbrains_connection":
|
||||
return build.initiator_name;
|
||||
case "autostart":
|
||||
case "autostop":
|
||||
@@ -87,6 +92,23 @@ export const getDisplayWorkspaceBuildInitiatedBy = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const systemBuildReasons = ["autostart", "autostop", "dormancy"];
|
||||
|
||||
export const buildReasonLabels: Record<TypesGen.BuildReason, string> = {
|
||||
// User build reasons
|
||||
initiator: "API",
|
||||
dashboard: "Dashboard",
|
||||
cli: "CLI",
|
||||
ssh_connection: "SSH Connection",
|
||||
vscode_connection: "VSCode Connection",
|
||||
jetbrains_connection: "JetBrains Connection",
|
||||
|
||||
// System build reasons
|
||||
autostart: "Autostart",
|
||||
autostop: "Autostop",
|
||||
dormancy: "Dormancy",
|
||||
};
|
||||
|
||||
const getWorkspaceBuildDurationInSeconds = (
|
||||
build: TypesGen.WorkspaceBuild,
|
||||
): number | undefined => {
|
||||
|
||||
Reference in New Issue
Block a user