mirror of https://github.com/coder/coder.git
feat(site): display user avatar (#11893)
* add owner API to workspace and workspace build responses * display user avatar in workspace top bar Co-authored-by: Cian Johnston <cian@coder.com>
This commit is contained in:
parent
83eea2d323
commit
dcab6fa5a4
|
@ -5,6 +5,7 @@
|
||||||
"updated_at": "[timestamp]",
|
"updated_at": "[timestamp]",
|
||||||
"owner_id": "[first user ID]",
|
"owner_id": "[first user ID]",
|
||||||
"owner_name": "testuser",
|
"owner_name": "testuser",
|
||||||
|
"owner_avatar_url": "",
|
||||||
"organization_id": "[first org ID]",
|
"organization_id": "[first org ID]",
|
||||||
"template_id": "[template ID]",
|
"template_id": "[template ID]",
|
||||||
"template_name": "test-template",
|
"template_name": "test-template",
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
"workspace_name": "test-workspace",
|
"workspace_name": "test-workspace",
|
||||||
"workspace_owner_id": "[first user ID]",
|
"workspace_owner_id": "[first user ID]",
|
||||||
"workspace_owner_name": "testuser",
|
"workspace_owner_name": "testuser",
|
||||||
|
"workspace_owner_avatar_url": "",
|
||||||
"template_version_id": "[version ID]",
|
"template_version_id": "[version ID]",
|
||||||
"template_version_name": "[version name]",
|
"template_version_name": "[version name]",
|
||||||
"build_number": 1,
|
"build_number": 1,
|
||||||
|
|
|
@ -12079,6 +12079,9 @@ const docTemplate = `{
|
||||||
"outdated": {
|
"outdated": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"owner_avatar_url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"owner_id": {
|
"owner_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uuid"
|
"format": "uuid"
|
||||||
|
@ -12656,6 +12659,9 @@ const docTemplate = `{
|
||||||
"workspace_name": {
|
"workspace_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"workspace_owner_avatar_url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"workspace_owner_id": {
|
"workspace_owner_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uuid"
|
"format": "uuid"
|
||||||
|
|
|
@ -10953,6 +10953,9 @@
|
||||||
"outdated": {
|
"outdated": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"owner_avatar_url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"owner_id": {
|
"owner_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uuid"
|
"format": "uuid"
|
||||||
|
@ -11501,6 +11504,9 @@
|
||||||
"workspace_name": {
|
"workspace_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"workspace_owner_avatar_url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"workspace_owner_id": {
|
"workspace_owner_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uuid"
|
"format": "uuid"
|
||||||
|
|
|
@ -63,12 +63,13 @@ func ExtractOrganizationParam(db database.Store) func(http.Handler) http.Handler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrganizationMember is the database object plus the Username. Including the Username in this
|
// OrganizationMember is the database object plus the Username and Avatar URL. Including these
|
||||||
// middleware is preferable to a join at the SQL layer so that we can keep the autogenerated
|
// in the middleware is preferable to a join at the SQL layer so that we can keep the
|
||||||
// database types as they are.
|
// autogenerated database types as they are.
|
||||||
type OrganizationMember struct {
|
type OrganizationMember struct {
|
||||||
database.OrganizationMember
|
database.OrganizationMember
|
||||||
Username string
|
Username string
|
||||||
|
AvatarURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractOrganizationMemberParam grabs a user membership from the "organization" and "user" URL parameter.
|
// ExtractOrganizationMemberParam grabs a user membership from the "organization" and "user" URL parameter.
|
||||||
|
@ -107,14 +108,17 @@ func ExtractOrganizationMemberParam(db database.Store) func(http.Handler) http.H
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, organizationMemberParamContextKey{}, OrganizationMember{
|
ctx = context.WithValue(ctx, organizationMemberParamContextKey{}, OrganizationMember{
|
||||||
OrganizationMember: organizationMember,
|
OrganizationMember: organizationMember,
|
||||||
// Here we're making one exception to the rule about not leaking data about the user
|
// Here we're making two exceptions to the rule about not leaking data about the user
|
||||||
// to the API handler, which is to include the username. If the caller has permission
|
// to the API handler, which is to include the username and avatar URL.
|
||||||
// to read the OrganizationMember, then we're explicitly saying here that they also
|
// If the caller has permission to read the OrganizationMember, then we're explicitly
|
||||||
// have permission to see the member's username, which is itself uncontroversial.
|
// saying here that they also have permission to see the member's username and avatar.
|
||||||
|
// This is OK!
|
||||||
//
|
//
|
||||||
// API handlers need this information for audit logging and returning the owner's
|
// API handlers need this information for audit logging and returning the owner's
|
||||||
// username in response to creating a workspace.
|
// username in response to creating a workspace. Additionally, the frontend consumes
|
||||||
Username: user.Username,
|
// the Avatar URL and this allows the FE to avoid an extra request.
|
||||||
|
Username: user.Username,
|
||||||
|
AvatarURL: user.AvatarURL,
|
||||||
})
|
})
|
||||||
next.ServeHTTP(rw, r.WithContext(ctx))
|
next.ServeHTTP(rw, r.WithContext(ctx))
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
|
@ -15,7 +16,9 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/database/dbmem"
|
"github.com/coder/coder/v2/coderd/database/dbmem"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/coderd/httpmw"
|
"github.com/coder/coder/v2/coderd/httpmw"
|
||||||
|
"github.com/coder/coder/v2/coderd/rbac"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOrganizationParam(t *testing.T) {
|
func TestOrganizationParam(t *testing.T) {
|
||||||
|
@ -139,6 +142,7 @@ func TestOrganizationParam(t *testing.T) {
|
||||||
t.Run("Success", func(t *testing.T) {
|
t.Run("Success", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
var (
|
var (
|
||||||
|
ctx = testutil.Context(t, testutil.WaitShort)
|
||||||
db = dbmem.New()
|
db = dbmem.New()
|
||||||
rw = httptest.NewRecorder()
|
rw = httptest.NewRecorder()
|
||||||
r, user = setupAuthentication(db)
|
r, user = setupAuthentication(db)
|
||||||
|
@ -148,7 +152,14 @@ func TestOrganizationParam(t *testing.T) {
|
||||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||||
OrganizationID: organization.ID,
|
OrganizationID: organization.ID,
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
|
Roles: []string{rbac.RoleOrgMember(organization.ID)},
|
||||||
})
|
})
|
||||||
|
_, err := db.UpdateUserRoles(ctx, database.UpdateUserRolesParams{
|
||||||
|
ID: user.ID,
|
||||||
|
GrantedRoles: []string{rbac.RoleTemplateAdmin()},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
chi.RouteContext(r.Context()).URLParams.Add("organization", organization.ID.String())
|
chi.RouteContext(r.Context()).URLParams.Add("organization", organization.ID.String())
|
||||||
chi.RouteContext(r.Context()).URLParams.Add("user", user.ID.String())
|
chi.RouteContext(r.Context()).URLParams.Add("user", user.ID.String())
|
||||||
rtr.Use(
|
rtr.Use(
|
||||||
|
@ -161,9 +172,27 @@ func TestOrganizationParam(t *testing.T) {
|
||||||
httpmw.ExtractOrganizationMemberParam(db),
|
httpmw.ExtractOrganizationMemberParam(db),
|
||||||
)
|
)
|
||||||
rtr.Get("/", func(rw http.ResponseWriter, r *http.Request) {
|
rtr.Get("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||||
_ = httpmw.OrganizationParam(r)
|
org := httpmw.OrganizationParam(r)
|
||||||
_ = httpmw.OrganizationMemberParam(r)
|
assert.NotZero(t, org)
|
||||||
|
assert.NotZero(t, org.CreatedAt)
|
||||||
|
// assert.NotZero(t, org.Description) // not supported
|
||||||
|
assert.NotZero(t, org.ID)
|
||||||
|
assert.NotEmpty(t, org.Name)
|
||||||
|
orgMem := httpmw.OrganizationMemberParam(r)
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
assert.NotZero(t, orgMem)
|
||||||
|
assert.NotZero(t, orgMem.CreatedAt)
|
||||||
|
assert.NotZero(t, orgMem.UpdatedAt)
|
||||||
|
assert.Equal(t, org.ID, orgMem.OrganizationID)
|
||||||
|
assert.Equal(t, user.ID, orgMem.UserID)
|
||||||
|
assert.Equal(t, user.Username, orgMem.Username)
|
||||||
|
assert.Equal(t, user.AvatarURL, orgMem.AvatarURL)
|
||||||
|
assert.NotEmpty(t, orgMem.Roles)
|
||||||
|
assert.NotZero(t, orgMem.OrganizationMember)
|
||||||
|
assert.NotEmpty(t, orgMem.OrganizationMember.CreatedAt)
|
||||||
|
assert.NotEmpty(t, orgMem.OrganizationMember.UpdatedAt)
|
||||||
|
assert.NotEmpty(t, orgMem.OrganizationMember.UserID)
|
||||||
|
assert.NotEmpty(t, orgMem.OrganizationMember.Roles)
|
||||||
})
|
})
|
||||||
rtr.ServeHTTP(rw, r)
|
rtr.ServeHTTP(rw, r)
|
||||||
res := rw.Result()
|
res := rw.Result()
|
||||||
|
|
|
@ -1271,13 +1271,13 @@ func userOrganizationIDs(ctx context.Context, api *API, user database.User) ([]u
|
||||||
return member.OrganizationIDs, nil
|
return member.OrganizationIDs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func usernameWithID(id uuid.UUID, users []database.User) (string, bool) {
|
func userByID(id uuid.UUID, users []database.User) (database.User, bool) {
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
if id == user.ID {
|
if id == user.ID {
|
||||||
return user.Username, true
|
return user, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", false
|
return database.User{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertAPIKey(k database.APIKey) codersdk.APIKey {
|
func convertAPIKey(k database.APIKey) codersdk.APIKey {
|
||||||
|
|
|
@ -69,7 +69,7 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) {
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ownerName, ok := usernameWithID(workspace.OwnerID, data.users)
|
owner, ok := userByID(workspace.OwnerID, data.users)
|
||||||
if !ok {
|
if !ok {
|
||||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
Message: "Internal error converting workspace build.",
|
Message: "Internal error converting workspace build.",
|
||||||
|
@ -82,7 +82,8 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) {
|
||||||
workspaceBuild,
|
workspaceBuild,
|
||||||
workspace,
|
workspace,
|
||||||
data.jobs[0],
|
data.jobs[0],
|
||||||
ownerName,
|
owner.Username,
|
||||||
|
owner.AvatarURL,
|
||||||
data.resources,
|
data.resources,
|
||||||
data.metadata,
|
data.metadata,
|
||||||
data.agents,
|
data.agents,
|
||||||
|
@ -283,7 +284,7 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ownerName, ok := usernameWithID(workspace.OwnerID, data.users)
|
owner, ok := userByID(workspace.OwnerID, data.users)
|
||||||
if !ok {
|
if !ok {
|
||||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
Message: "Internal error converting workspace build.",
|
Message: "Internal error converting workspace build.",
|
||||||
|
@ -296,7 +297,8 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ
|
||||||
workspaceBuild,
|
workspaceBuild,
|
||||||
workspace,
|
workspace,
|
||||||
data.jobs[0],
|
data.jobs[0],
|
||||||
ownerName,
|
owner.Username,
|
||||||
|
owner.AvatarURL,
|
||||||
data.resources,
|
data.resources,
|
||||||
data.metadata,
|
data.metadata,
|
||||||
data.agents,
|
data.agents,
|
||||||
|
@ -416,7 +418,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ownerName, exists := usernameWithID(workspace.OwnerID, users)
|
owner, exists := userByID(workspace.OwnerID, users)
|
||||||
if !exists {
|
if !exists {
|
||||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
Message: "Internal error converting workspace build.",
|
Message: "Internal error converting workspace build.",
|
||||||
|
@ -432,7 +434,8 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||||
ProvisionerJob: *provisionerJob,
|
ProvisionerJob: *provisionerJob,
|
||||||
QueuePosition: 0,
|
QueuePosition: 0,
|
||||||
},
|
},
|
||||||
ownerName,
|
owner.Username,
|
||||||
|
owner.AvatarURL,
|
||||||
[]database.WorkspaceResource{},
|
[]database.WorkspaceResource{},
|
||||||
[]database.WorkspaceResourceMetadatum{},
|
[]database.WorkspaceResourceMetadatum{},
|
||||||
[]database.WorkspaceAgent{},
|
[]database.WorkspaceAgent{},
|
||||||
|
@ -833,7 +836,7 @@ func (api *API) convertWorkspaceBuilds(
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, xerrors.New("template version not found")
|
return nil, xerrors.New("template version not found")
|
||||||
}
|
}
|
||||||
ownerName, exists := usernameWithID(workspace.OwnerID, users)
|
owner, exists := userByID(workspace.OwnerID, users)
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, xerrors.Errorf("owner not found for workspace: %q", workspace.Name)
|
return nil, xerrors.Errorf("owner not found for workspace: %q", workspace.Name)
|
||||||
}
|
}
|
||||||
|
@ -842,7 +845,8 @@ func (api *API) convertWorkspaceBuilds(
|
||||||
build,
|
build,
|
||||||
workspace,
|
workspace,
|
||||||
job,
|
job,
|
||||||
ownerName,
|
owner.Username,
|
||||||
|
owner.AvatarURL,
|
||||||
workspaceResources,
|
workspaceResources,
|
||||||
resourceMetadata,
|
resourceMetadata,
|
||||||
resourceAgents,
|
resourceAgents,
|
||||||
|
@ -865,7 +869,7 @@ func (api *API) convertWorkspaceBuild(
|
||||||
build database.WorkspaceBuild,
|
build database.WorkspaceBuild,
|
||||||
workspace database.Workspace,
|
workspace database.Workspace,
|
||||||
job database.GetProvisionerJobsByIDsWithQueuePositionRow,
|
job database.GetProvisionerJobsByIDsWithQueuePositionRow,
|
||||||
ownerName string,
|
username, avatarURL string,
|
||||||
workspaceResources []database.WorkspaceResource,
|
workspaceResources []database.WorkspaceResource,
|
||||||
resourceMetadata []database.WorkspaceResourceMetadatum,
|
resourceMetadata []database.WorkspaceResourceMetadatum,
|
||||||
resourceAgents []database.WorkspaceAgent,
|
resourceAgents []database.WorkspaceAgent,
|
||||||
|
@ -909,7 +913,7 @@ func (api *API) convertWorkspaceBuild(
|
||||||
scripts := scriptsByAgentID[agent.ID]
|
scripts := scriptsByAgentID[agent.ID]
|
||||||
logSources := logSourcesByAgentID[agent.ID]
|
logSources := logSourcesByAgentID[agent.ID]
|
||||||
apiAgent, err := db2sdk.WorkspaceAgent(
|
apiAgent, err := db2sdk.WorkspaceAgent(
|
||||||
api.DERPMap(), *api.TailnetCoordinator.Load(), agent, db2sdk.Apps(apps, agent, ownerName, workspace), convertScripts(scripts), convertLogSources(logSources), api.AgentInactiveDisconnectTimeout,
|
api.DERPMap(), *api.TailnetCoordinator.Load(), agent, db2sdk.Apps(apps, agent, username, workspace), convertScripts(scripts), convertLogSources(logSources), api.AgentInactiveDisconnectTimeout,
|
||||||
api.DeploymentValues.AgentFallbackTroubleshootingURL.String(),
|
api.DeploymentValues.AgentFallbackTroubleshootingURL.String(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -923,26 +927,27 @@ func (api *API) convertWorkspaceBuild(
|
||||||
apiJob := convertProvisionerJob(job)
|
apiJob := convertProvisionerJob(job)
|
||||||
transition := codersdk.WorkspaceTransition(build.Transition)
|
transition := codersdk.WorkspaceTransition(build.Transition)
|
||||||
return codersdk.WorkspaceBuild{
|
return codersdk.WorkspaceBuild{
|
||||||
ID: build.ID,
|
ID: build.ID,
|
||||||
CreatedAt: build.CreatedAt,
|
CreatedAt: build.CreatedAt,
|
||||||
UpdatedAt: build.UpdatedAt,
|
UpdatedAt: build.UpdatedAt,
|
||||||
WorkspaceOwnerID: workspace.OwnerID,
|
WorkspaceOwnerID: workspace.OwnerID,
|
||||||
WorkspaceOwnerName: ownerName,
|
WorkspaceOwnerName: username,
|
||||||
WorkspaceID: build.WorkspaceID,
|
WorkspaceOwnerAvatarURL: avatarURL,
|
||||||
WorkspaceName: workspace.Name,
|
WorkspaceID: build.WorkspaceID,
|
||||||
TemplateVersionID: build.TemplateVersionID,
|
WorkspaceName: workspace.Name,
|
||||||
TemplateVersionName: templateVersion.Name,
|
TemplateVersionID: build.TemplateVersionID,
|
||||||
BuildNumber: build.BuildNumber,
|
TemplateVersionName: templateVersion.Name,
|
||||||
Transition: transition,
|
BuildNumber: build.BuildNumber,
|
||||||
InitiatorID: build.InitiatorID,
|
Transition: transition,
|
||||||
InitiatorUsername: build.InitiatorByUsername,
|
InitiatorID: build.InitiatorID,
|
||||||
Job: apiJob,
|
InitiatorUsername: build.InitiatorByUsername,
|
||||||
Deadline: codersdk.NewNullTime(build.Deadline, !build.Deadline.IsZero()),
|
Job: apiJob,
|
||||||
MaxDeadline: codersdk.NewNullTime(build.MaxDeadline, !build.MaxDeadline.IsZero()),
|
Deadline: codersdk.NewNullTime(build.Deadline, !build.Deadline.IsZero()),
|
||||||
Reason: codersdk.BuildReason(build.Reason),
|
MaxDeadline: codersdk.NewNullTime(build.MaxDeadline, !build.MaxDeadline.IsZero()),
|
||||||
Resources: apiResources,
|
Reason: codersdk.BuildReason(build.Reason),
|
||||||
Status: convertWorkspaceStatus(apiJob.Status, transition),
|
Resources: apiResources,
|
||||||
DailyCost: build.DailyCost,
|
Status: convertWorkspaceStatus(apiJob.Status, transition),
|
||||||
|
DailyCost: build.DailyCost,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/audit"
|
"github.com/coder/coder/v2/coderd/audit"
|
||||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
|
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
"github.com/coder/coder/v2/coderd/rbac"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
@ -37,12 +38,23 @@ func TestWorkspaceBuild(t *testing.T) {
|
||||||
propagation.Baggage{},
|
propagation.Baggage{},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
auditor := audit.NewMock()
|
auditor := audit.NewMock()
|
||||||
client := coderdtest.New(t, &coderdtest.Options{
|
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
||||||
IncludeProvisionerDaemon: true,
|
IncludeProvisionerDaemon: true,
|
||||||
Auditor: auditor,
|
Auditor: auditor,
|
||||||
})
|
})
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
//nolint:gocritic // testing
|
||||||
|
up, err := db.UpdateUserProfile(dbauthz.AsSystemRestricted(ctx), database.UpdateUserProfileParams{
|
||||||
|
ID: user.UserID,
|
||||||
|
Email: coderdtest.FirstUserParams.Email,
|
||||||
|
Username: coderdtest.FirstUserParams.Username,
|
||||||
|
Name: "Admin",
|
||||||
|
AvatarURL: client.URL.String(),
|
||||||
|
UpdatedAt: dbtime.Now(),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
@ -57,6 +69,10 @@ func TestWorkspaceBuild(t *testing.T) {
|
||||||
assert.Equal(t, logs[0].Ip.IPNet.IP.String(), "127.0.0.1") &&
|
assert.Equal(t, logs[0].Ip.IPNet.IP.String(), "127.0.0.1") &&
|
||||||
assert.Equal(t, logs[1].Ip.IPNet.IP.String(), "127.0.0.1")
|
assert.Equal(t, logs[1].Ip.IPNet.IP.String(), "127.0.0.1")
|
||||||
}, testutil.WaitShort, testutil.IntervalFast)
|
}, testutil.WaitShort, testutil.IntervalFast)
|
||||||
|
wb, err := client.WorkspaceBuild(testutil.Context(t, testutil.WaitShort), workspace.LatestBuild.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, up.Username, wb.WorkspaceOwnerName)
|
||||||
|
require.Equal(t, up.AvatarURL, wb.WorkspaceOwnerAvatarURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkspaceBuildByBuildNumber(t *testing.T) {
|
func TestWorkspaceBuildByBuildNumber(t *testing.T) {
|
||||||
|
|
|
@ -94,7 +94,7 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
|
||||||
httpapi.Forbidden(rw)
|
httpapi.Forbidden(rw)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ownerName, ok := usernameWithID(workspace.OwnerID, data.users)
|
owner, ok := userByID(workspace.OwnerID, data.users)
|
||||||
if !ok {
|
if !ok {
|
||||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
Message: "Internal error fetching workspace resources.",
|
Message: "Internal error fetching workspace resources.",
|
||||||
|
@ -108,7 +108,8 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
|
||||||
workspace,
|
workspace,
|
||||||
data.builds[0],
|
data.builds[0],
|
||||||
data.templates[0],
|
data.templates[0],
|
||||||
ownerName,
|
owner.Username,
|
||||||
|
owner.AvatarURL,
|
||||||
api.Options.AllowWorkspaceRenames,
|
api.Options.AllowWorkspaceRenames,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -281,7 +282,7 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
|
||||||
httpapi.ResourceNotFound(rw)
|
httpapi.ResourceNotFound(rw)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ownerName, ok := usernameWithID(workspace.OwnerID, data.users)
|
owner, ok := userByID(workspace.OwnerID, data.users)
|
||||||
if !ok {
|
if !ok {
|
||||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
Message: "Internal error fetching workspace resources.",
|
Message: "Internal error fetching workspace resources.",
|
||||||
|
@ -294,7 +295,8 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
|
||||||
workspace,
|
workspace,
|
||||||
data.builds[0],
|
data.builds[0],
|
||||||
data.templates[0],
|
data.templates[0],
|
||||||
ownerName,
|
owner.Username,
|
||||||
|
owner.AvatarURL,
|
||||||
api.Options.AllowWorkspaceRenames,
|
api.Options.AllowWorkspaceRenames,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -591,6 +593,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
|
||||||
QueuePosition: 0,
|
QueuePosition: 0,
|
||||||
},
|
},
|
||||||
member.Username,
|
member.Username,
|
||||||
|
member.AvatarURL,
|
||||||
[]database.WorkspaceResource{},
|
[]database.WorkspaceResource{},
|
||||||
[]database.WorkspaceResourceMetadatum{},
|
[]database.WorkspaceResourceMetadatum{},
|
||||||
[]database.WorkspaceAgent{},
|
[]database.WorkspaceAgent{},
|
||||||
|
@ -613,6 +616,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
|
||||||
apiBuild,
|
apiBuild,
|
||||||
template,
|
template,
|
||||||
member.Username,
|
member.Username,
|
||||||
|
member.AvatarURL,
|
||||||
api.Options.AllowWorkspaceRenames,
|
api.Options.AllowWorkspaceRenames,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -941,7 +945,7 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) {
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ownerName, ok := usernameWithID(workspace.OwnerID, data.users)
|
owner, ok := userByID(workspace.OwnerID, data.users)
|
||||||
if !ok {
|
if !ok {
|
||||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
Message: "Internal error fetching workspace resources.",
|
Message: "Internal error fetching workspace resources.",
|
||||||
|
@ -962,7 +966,8 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) {
|
||||||
workspace,
|
workspace,
|
||||||
data.builds[0],
|
data.builds[0],
|
||||||
data.templates[0],
|
data.templates[0],
|
||||||
ownerName,
|
owner.Username,
|
||||||
|
owner.AvatarURL,
|
||||||
api.Options.AllowWorkspaceRenames,
|
api.Options.AllowWorkspaceRenames,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1372,7 +1377,7 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ownerName, ok := usernameWithID(workspace.OwnerID, data.users)
|
owner, ok := userByID(workspace.OwnerID, data.users)
|
||||||
if !ok {
|
if !ok {
|
||||||
_ = sendEvent(ctx, codersdk.ServerSentEvent{
|
_ = sendEvent(ctx, codersdk.ServerSentEvent{
|
||||||
Type: codersdk.ServerSentEventTypeError,
|
Type: codersdk.ServerSentEventTypeError,
|
||||||
|
@ -1389,7 +1394,8 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) {
|
||||||
workspace,
|
workspace,
|
||||||
data.builds[0],
|
data.builds[0],
|
||||||
data.templates[0],
|
data.templates[0],
|
||||||
ownerName,
|
owner.Username,
|
||||||
|
owner.AvatarURL,
|
||||||
api.Options.AllowWorkspaceRenames,
|
api.Options.AllowWorkspaceRenames,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1556,6 +1562,7 @@ func convertWorkspaces(requesterID uuid.UUID, workspaces []database.Workspace, d
|
||||||
build,
|
build,
|
||||||
template,
|
template,
|
||||||
owner.Username,
|
owner.Username,
|
||||||
|
owner.AvatarURL,
|
||||||
data.allowRenames,
|
data.allowRenames,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1572,7 +1579,8 @@ func convertWorkspace(
|
||||||
workspace database.Workspace,
|
workspace database.Workspace,
|
||||||
workspaceBuild codersdk.WorkspaceBuild,
|
workspaceBuild codersdk.WorkspaceBuild,
|
||||||
template database.Template,
|
template database.Template,
|
||||||
ownerName string,
|
username string,
|
||||||
|
avatarURL string,
|
||||||
allowRenames bool,
|
allowRenames bool,
|
||||||
) (codersdk.Workspace, error) {
|
) (codersdk.Workspace, error) {
|
||||||
if requesterID == uuid.Nil {
|
if requesterID == uuid.Nil {
|
||||||
|
@ -1612,7 +1620,8 @@ func convertWorkspace(
|
||||||
CreatedAt: workspace.CreatedAt,
|
CreatedAt: workspace.CreatedAt,
|
||||||
UpdatedAt: workspace.UpdatedAt,
|
UpdatedAt: workspace.UpdatedAt,
|
||||||
OwnerID: workspace.OwnerID,
|
OwnerID: workspace.OwnerID,
|
||||||
OwnerName: ownerName,
|
OwnerName: username,
|
||||||
|
OwnerAvatarURL: avatarURL,
|
||||||
OrganizationID: workspace.OrganizationID,
|
OrganizationID: workspace.OrganizationID,
|
||||||
TemplateID: workspace.TemplateID,
|
TemplateID: workspace.TemplateID,
|
||||||
LatestBuild: workspaceBuild,
|
LatestBuild: workspaceBuild,
|
||||||
|
|
|
@ -51,26 +51,27 @@ const (
|
||||||
// WorkspaceBuild is an at-point representation of a workspace state.
|
// WorkspaceBuild is an at-point representation of a workspace state.
|
||||||
// BuildNumbers start at 1 and increase by 1 for each subsequent build
|
// BuildNumbers start at 1 and increase by 1 for each subsequent build
|
||||||
type WorkspaceBuild struct {
|
type WorkspaceBuild struct {
|
||||||
ID uuid.UUID `json:"id" format:"uuid"`
|
ID uuid.UUID `json:"id" format:"uuid"`
|
||||||
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
||||||
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
|
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
|
||||||
WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid"`
|
WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid"`
|
||||||
WorkspaceName string `json:"workspace_name"`
|
WorkspaceName string `json:"workspace_name"`
|
||||||
WorkspaceOwnerID uuid.UUID `json:"workspace_owner_id" format:"uuid"`
|
WorkspaceOwnerID uuid.UUID `json:"workspace_owner_id" format:"uuid"`
|
||||||
WorkspaceOwnerName string `json:"workspace_owner_name"`
|
WorkspaceOwnerName string `json:"workspace_owner_name"`
|
||||||
TemplateVersionID uuid.UUID `json:"template_version_id" format:"uuid"`
|
WorkspaceOwnerAvatarURL string `json:"workspace_owner_avatar_url"`
|
||||||
TemplateVersionName string `json:"template_version_name"`
|
TemplateVersionID uuid.UUID `json:"template_version_id" format:"uuid"`
|
||||||
BuildNumber int32 `json:"build_number"`
|
TemplateVersionName string `json:"template_version_name"`
|
||||||
Transition WorkspaceTransition `json:"transition" enums:"start,stop,delete"`
|
BuildNumber int32 `json:"build_number"`
|
||||||
InitiatorID uuid.UUID `json:"initiator_id" format:"uuid"`
|
Transition WorkspaceTransition `json:"transition" enums:"start,stop,delete"`
|
||||||
InitiatorUsername string `json:"initiator_name"`
|
InitiatorID uuid.UUID `json:"initiator_id" format:"uuid"`
|
||||||
Job ProvisionerJob `json:"job"`
|
InitiatorUsername string `json:"initiator_name"`
|
||||||
Reason BuildReason `db:"reason" json:"reason" enums:"initiator,autostart,autostop"`
|
Job ProvisionerJob `json:"job"`
|
||||||
Resources []WorkspaceResource `json:"resources"`
|
Reason BuildReason `db:"reason" json:"reason" enums:"initiator,autostart,autostop"`
|
||||||
Deadline NullTime `json:"deadline,omitempty" format:"date-time"`
|
Resources []WorkspaceResource `json:"resources"`
|
||||||
MaxDeadline NullTime `json:"max_deadline,omitempty" format:"date-time"`
|
Deadline NullTime `json:"deadline,omitempty" format:"date-time"`
|
||||||
Status WorkspaceStatus `json:"status" enums:"pending,starting,running,stopping,stopped,failed,canceling,canceled,deleting,deleted"`
|
MaxDeadline NullTime `json:"max_deadline,omitempty" format:"date-time"`
|
||||||
DailyCost int32 `json:"daily_cost"`
|
Status WorkspaceStatus `json:"status" enums:"pending,starting,running,stopping,stopped,failed,canceling,canceled,deleting,deleted"`
|
||||||
|
DailyCost int32 `json:"daily_cost"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WorkspaceResource describes resources used to create a workspace, for instance:
|
// WorkspaceResource describes resources used to create a workspace, for instance:
|
||||||
|
|
|
@ -29,6 +29,7 @@ type Workspace struct {
|
||||||
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
|
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
|
||||||
OwnerID uuid.UUID `json:"owner_id" format:"uuid"`
|
OwnerID uuid.UUID `json:"owner_id" format:"uuid"`
|
||||||
OwnerName string `json:"owner_name"`
|
OwnerName string `json:"owner_name"`
|
||||||
|
OwnerAvatarURL string `json:"owner_avatar_url"`
|
||||||
OrganizationID uuid.UUID `json:"organization_id" format:"uuid"`
|
OrganizationID uuid.UUID `json:"organization_id" format:"uuid"`
|
||||||
TemplateID uuid.UUID `json:"template_id" format:"uuid"`
|
TemplateID uuid.UUID `json:"template_id" format:"uuid"`
|
||||||
TemplateName string `json:"template_name"`
|
TemplateName string `json:"template_name"`
|
||||||
|
|
|
@ -170,6 +170,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
}
|
}
|
||||||
|
@ -351,6 +352,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
}
|
}
|
||||||
|
@ -960,6 +962,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
}
|
}
|
||||||
|
@ -1146,6 +1149,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
}
|
}
|
||||||
|
@ -1277,6 +1281,7 @@ Status Code **200**
|
||||||
| `» updated_at` | string(date-time) | false | | |
|
| `» updated_at` | string(date-time) | false | | |
|
||||||
| `» workspace_id` | string(uuid) | false | | |
|
| `» workspace_id` | string(uuid) | false | | |
|
||||||
| `» workspace_name` | string | false | | |
|
| `» workspace_name` | string | false | | |
|
||||||
|
| `» workspace_owner_avatar_url` | string | false | | |
|
||||||
| `» workspace_owner_id` | string(uuid) | false | | |
|
| `» workspace_owner_id` | string(uuid) | false | | |
|
||||||
| `» workspace_owner_name` | string | false | | |
|
| `» workspace_owner_name` | string | false | | |
|
||||||
|
|
||||||
|
@ -1524,6 +1529,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
}
|
}
|
||||||
|
|
|
@ -6096,12 +6096,14 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
},
|
},
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"outdated": true,
|
"outdated": true,
|
||||||
|
"owner_avatar_url": "string",
|
||||||
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
||||||
"owner_name": "string",
|
"owner_name": "string",
|
||||||
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
||||||
|
@ -6134,6 +6136,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||||
| `name` | string | false | | |
|
| `name` | string | false | | |
|
||||||
| `organization_id` | string | false | | |
|
| `organization_id` | string | false | | |
|
||||||
| `outdated` | boolean | false | | |
|
| `outdated` | boolean | false | | |
|
||||||
|
| `owner_avatar_url` | string | false | | |
|
||||||
| `owner_id` | string | false | | |
|
| `owner_id` | string | false | | |
|
||||||
| `owner_name` | string | false | | |
|
| `owner_name` | string | false | | |
|
||||||
| `template_active_version_id` | string | false | | |
|
| `template_active_version_id` | string | false | | |
|
||||||
|
@ -6790,6 +6793,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
}
|
}
|
||||||
|
@ -6797,28 +6801,29 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
| Name | Type | Required | Restrictions | Description |
|
| Name | Type | Required | Restrictions | Description |
|
||||||
| ----------------------- | ----------------------------------------------------------------- | -------- | ------------ | ----------- |
|
| ---------------------------- | ----------------------------------------------------------------- | -------- | ------------ | ----------- |
|
||||||
| `build_number` | integer | false | | |
|
| `build_number` | integer | false | | |
|
||||||
| `created_at` | string | false | | |
|
| `created_at` | string | false | | |
|
||||||
| `daily_cost` | integer | false | | |
|
| `daily_cost` | integer | false | | |
|
||||||
| `deadline` | string | false | | |
|
| `deadline` | string | false | | |
|
||||||
| `id` | string | false | | |
|
| `id` | string | false | | |
|
||||||
| `initiator_id` | string | false | | |
|
| `initiator_id` | string | false | | |
|
||||||
| `initiator_name` | string | false | | |
|
| `initiator_name` | string | false | | |
|
||||||
| `job` | [codersdk.ProvisionerJob](#codersdkprovisionerjob) | false | | |
|
| `job` | [codersdk.ProvisionerJob](#codersdkprovisionerjob) | false | | |
|
||||||
| `max_deadline` | string | false | | |
|
| `max_deadline` | string | false | | |
|
||||||
| `reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | |
|
| `reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | |
|
||||||
| `resources` | array of [codersdk.WorkspaceResource](#codersdkworkspaceresource) | false | | |
|
| `resources` | array of [codersdk.WorkspaceResource](#codersdkworkspaceresource) | false | | |
|
||||||
| `status` | [codersdk.WorkspaceStatus](#codersdkworkspacestatus) | false | | |
|
| `status` | [codersdk.WorkspaceStatus](#codersdkworkspacestatus) | false | | |
|
||||||
| `template_version_id` | string | false | | |
|
| `template_version_id` | string | false | | |
|
||||||
| `template_version_name` | string | false | | |
|
| `template_version_name` | string | false | | |
|
||||||
| `transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | false | | |
|
| `transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | false | | |
|
||||||
| `updated_at` | string | false | | |
|
| `updated_at` | string | false | | |
|
||||||
| `workspace_id` | string | false | | |
|
| `workspace_id` | string | false | | |
|
||||||
| `workspace_name` | string | false | | |
|
| `workspace_name` | string | false | | |
|
||||||
| `workspace_owner_id` | string | false | | |
|
| `workspace_owner_avatar_url` | string | false | | |
|
||||||
| `workspace_owner_name` | string | false | | |
|
| `workspace_owner_id` | string | false | | |
|
||||||
|
| `workspace_owner_name` | string | false | | |
|
||||||
|
|
||||||
#### Enumerated Values
|
#### Enumerated Values
|
||||||
|
|
||||||
|
@ -7357,12 +7362,14 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
},
|
},
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"outdated": true,
|
"outdated": true,
|
||||||
|
"owner_avatar_url": "string",
|
||||||
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
||||||
"owner_name": "string",
|
"owner_name": "string",
|
||||||
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
||||||
|
|
|
@ -204,12 +204,14 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
},
|
},
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"outdated": true,
|
"outdated": true,
|
||||||
|
"owner_avatar_url": "string",
|
||||||
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
||||||
"owner_name": "string",
|
"owner_name": "string",
|
||||||
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
||||||
|
@ -416,12 +418,14 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
},
|
},
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"outdated": true,
|
"outdated": true,
|
||||||
|
"owner_avatar_url": "string",
|
||||||
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
||||||
"owner_name": "string",
|
"owner_name": "string",
|
||||||
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
||||||
|
@ -627,12 +631,14 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
},
|
},
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"outdated": true,
|
"outdated": true,
|
||||||
|
"owner_avatar_url": "string",
|
||||||
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
||||||
"owner_name": "string",
|
"owner_name": "string",
|
||||||
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
||||||
|
@ -840,12 +846,14 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
},
|
},
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"outdated": true,
|
"outdated": true,
|
||||||
|
"owner_avatar_url": "string",
|
||||||
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
||||||
"owner_name": "string",
|
"owner_name": "string",
|
||||||
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
||||||
|
@ -1168,12 +1176,14 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/dormant \
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
"workspace_name": "string",
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
"workspace_owner_name": "string"
|
"workspace_owner_name": "string"
|
||||||
},
|
},
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"outdated": true,
|
"outdated": true,
|
||||||
|
"owner_avatar_url": "string",
|
||||||
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
|
||||||
"owner_name": "string",
|
"owner_name": "string",
|
||||||
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
"template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c",
|
||||||
|
|
|
@ -1481,6 +1481,7 @@ export interface Workspace {
|
||||||
readonly updated_at: string;
|
readonly updated_at: string;
|
||||||
readonly owner_id: string;
|
readonly owner_id: string;
|
||||||
readonly owner_name: string;
|
readonly owner_name: string;
|
||||||
|
readonly owner_avatar_url: string;
|
||||||
readonly organization_id: string;
|
readonly organization_id: string;
|
||||||
readonly template_id: string;
|
readonly template_id: string;
|
||||||
readonly template_name: string;
|
readonly template_name: string;
|
||||||
|
@ -1635,6 +1636,7 @@ export interface WorkspaceBuild {
|
||||||
readonly workspace_name: string;
|
readonly workspace_name: string;
|
||||||
readonly workspace_owner_id: string;
|
readonly workspace_owner_id: string;
|
||||||
readonly workspace_owner_name: string;
|
readonly workspace_owner_name: string;
|
||||||
|
readonly workspace_owner_avatar_url: string;
|
||||||
readonly template_version_id: string;
|
readonly template_version_id: string;
|
||||||
readonly template_version_name: string;
|
readonly template_version_name: string;
|
||||||
readonly build_number: number;
|
readonly build_number: number;
|
||||||
|
|
|
@ -22,7 +22,7 @@ WebAuthenticated.args = {
|
||||||
app_installable: false,
|
app_installable: false,
|
||||||
display_name: "BitBucket",
|
display_name: "BitBucket",
|
||||||
user: {
|
user: {
|
||||||
avatar_url: "",
|
avatar_url: "https://avatars.githubusercontent.com/u/7122116?v=4",
|
||||||
login: "kylecarbs",
|
login: "kylecarbs",
|
||||||
name: "Kyle Carberry",
|
name: "Kyle Carberry",
|
||||||
profile_url: "",
|
profile_url: "",
|
||||||
|
@ -83,7 +83,7 @@ DeviceAuthenticatedNotInstalled.args = {
|
||||||
app_install_url: "https://example.com",
|
app_install_url: "https://example.com",
|
||||||
app_installable: true,
|
app_installable: true,
|
||||||
user: {
|
user: {
|
||||||
avatar_url: "",
|
avatar_url: "https://avatars.githubusercontent.com/u/7122116?v=4",
|
||||||
login: "kylecarbs",
|
login: "kylecarbs",
|
||||||
name: "Kyle Carberry",
|
name: "Kyle Carberry",
|
||||||
profile_url: "",
|
profile_url: "",
|
||||||
|
@ -112,7 +112,7 @@ DeviceAuthenticatedInstalled.args = {
|
||||||
app_install_url: "https://example.com",
|
app_install_url: "https://example.com",
|
||||||
app_installable: true,
|
app_installable: true,
|
||||||
user: {
|
user: {
|
||||||
avatar_url: "",
|
avatar_url: "https://avatars.githubusercontent.com/u/7122116?v=4",
|
||||||
login: "kylecarbs",
|
login: "kylecarbs",
|
||||||
name: "Kyle Carberry",
|
name: "Kyle Carberry",
|
||||||
profile_url: "",
|
profile_url: "",
|
||||||
|
|
|
@ -2,7 +2,6 @@ import Tooltip from "@mui/material/Tooltip";
|
||||||
import Link from "@mui/material/Link";
|
import Link from "@mui/material/Link";
|
||||||
import MonetizationOnOutlined from "@mui/icons-material/MonetizationOnOutlined";
|
import MonetizationOnOutlined from "@mui/icons-material/MonetizationOnOutlined";
|
||||||
import DeleteOutline from "@mui/icons-material/DeleteOutline";
|
import DeleteOutline from "@mui/icons-material/DeleteOutline";
|
||||||
import PersonOutline from "@mui/icons-material/PersonOutline";
|
|
||||||
import ArrowBackOutlined from "@mui/icons-material/ArrowBackOutlined";
|
import ArrowBackOutlined from "@mui/icons-material/ArrowBackOutlined";
|
||||||
import ScheduleOutlined from "@mui/icons-material/ScheduleOutlined";
|
import ScheduleOutlined from "@mui/icons-material/ScheduleOutlined";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
|
@ -33,6 +32,7 @@ import { ExternalAvatar } from "components/Avatar/Avatar";
|
||||||
import { WorkspaceActions } from "./WorkspaceActions/WorkspaceActions";
|
import { WorkspaceActions } from "./WorkspaceActions/WorkspaceActions";
|
||||||
import { WorkspaceNotifications } from "./WorkspaceNotifications/WorkspaceNotifications";
|
import { WorkspaceNotifications } from "./WorkspaceNotifications/WorkspaceNotifications";
|
||||||
import { WorkspacePermissions } from "./permissions";
|
import { WorkspacePermissions } from "./permissions";
|
||||||
|
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||||
|
|
||||||
export type WorkspaceError =
|
export type WorkspaceError =
|
||||||
| "getBuildsError"
|
| "getBuildsError"
|
||||||
|
@ -130,9 +130,11 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TopbarData>
|
<TopbarData>
|
||||||
<TopbarIcon>
|
<UserAvatar
|
||||||
<PersonOutline />
|
size="xs"
|
||||||
</TopbarIcon>
|
username={workspace.owner_name}
|
||||||
|
avatarURL={workspace.owner_avatar_url}
|
||||||
|
/>
|
||||||
<Tooltip title="Owner">
|
<Tooltip title="Owner">
|
||||||
<span>{workspace.owner_name}</span>
|
<span>{workspace.owner_name}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
@ -897,6 +897,7 @@ export const MockWorkspaceBuild: TypesGen.WorkspaceBuild = {
|
||||||
workspace_name: "test-workspace",
|
workspace_name: "test-workspace",
|
||||||
workspace_owner_id: MockUser.id,
|
workspace_owner_id: MockUser.id,
|
||||||
workspace_owner_name: MockUser.username,
|
workspace_owner_name: MockUser.username,
|
||||||
|
workspace_owner_avatar_url: MockUser.avatar_url,
|
||||||
workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3",
|
workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3",
|
||||||
deadline: "2022-05-17T23:39:00.00Z",
|
deadline: "2022-05-17T23:39:00.00Z",
|
||||||
reason: "initiator",
|
reason: "initiator",
|
||||||
|
@ -919,6 +920,7 @@ export const MockWorkspaceBuildAutostart: TypesGen.WorkspaceBuild = {
|
||||||
workspace_name: "test-workspace",
|
workspace_name: "test-workspace",
|
||||||
workspace_owner_id: MockUser.id,
|
workspace_owner_id: MockUser.id,
|
||||||
workspace_owner_name: MockUser.username,
|
workspace_owner_name: MockUser.username,
|
||||||
|
workspace_owner_avatar_url: MockUser.avatar_url,
|
||||||
workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3",
|
workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3",
|
||||||
deadline: "2022-05-17T23:39:00.00Z",
|
deadline: "2022-05-17T23:39:00.00Z",
|
||||||
reason: "autostart",
|
reason: "autostart",
|
||||||
|
@ -941,6 +943,7 @@ export const MockWorkspaceBuildAutostop: TypesGen.WorkspaceBuild = {
|
||||||
workspace_name: "test-workspace",
|
workspace_name: "test-workspace",
|
||||||
workspace_owner_id: MockUser.id,
|
workspace_owner_id: MockUser.id,
|
||||||
workspace_owner_name: MockUser.username,
|
workspace_owner_name: MockUser.username,
|
||||||
|
workspace_owner_avatar_url: MockUser.avatar_url,
|
||||||
workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3",
|
workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3",
|
||||||
deadline: "2022-05-17T23:39:00.00Z",
|
deadline: "2022-05-17T23:39:00.00Z",
|
||||||
reason: "autostop",
|
reason: "autostop",
|
||||||
|
@ -965,6 +968,7 @@ export const MockFailedWorkspaceBuild = (
|
||||||
workspace_name: "test-workspace",
|
workspace_name: "test-workspace",
|
||||||
workspace_owner_id: MockUser.id,
|
workspace_owner_id: MockUser.id,
|
||||||
workspace_owner_name: MockUser.username,
|
workspace_owner_name: MockUser.username,
|
||||||
|
workspace_owner_avatar_url: MockUser.avatar_url,
|
||||||
workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3",
|
workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3",
|
||||||
deadline: "2022-05-17T23:39:00.00Z",
|
deadline: "2022-05-17T23:39:00.00Z",
|
||||||
reason: "initiator",
|
reason: "initiator",
|
||||||
|
@ -1010,6 +1014,7 @@ export const MockWorkspace: TypesGen.Workspace = {
|
||||||
owner_id: MockUser.id,
|
owner_id: MockUser.id,
|
||||||
organization_id: MockOrganization.id,
|
organization_id: MockOrganization.id,
|
||||||
owner_name: MockUser.username,
|
owner_name: MockUser.username,
|
||||||
|
owner_avatar_url: "https://avatars.githubusercontent.com/u/7122116?v=4",
|
||||||
autostart_schedule: MockWorkspaceAutostartEnabled.schedule,
|
autostart_schedule: MockWorkspaceAutostartEnabled.schedule,
|
||||||
ttl_ms: 2 * 60 * 60 * 1000,
|
ttl_ms: 2 * 60 * 60 * 1000,
|
||||||
latest_build: MockWorkspaceBuild,
|
latest_build: MockWorkspaceBuild,
|
||||||
|
|
Loading…
Reference in New Issue