mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
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:
6
coderd/apidoc/docs.go
generated
6
coderd/apidoc/docs.go
generated
@ -12079,6 +12079,9 @@ const docTemplate = `{
|
||||
"outdated": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"owner_avatar_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
@ -12656,6 +12659,9 @@ const docTemplate = `{
|
||||
"workspace_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"workspace_owner_avatar_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"workspace_owner_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
|
6
coderd/apidoc/swagger.json
generated
6
coderd/apidoc/swagger.json
generated
@ -10953,6 +10953,9 @@
|
||||
"outdated": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"owner_avatar_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
@ -11501,6 +11504,9 @@
|
||||
"workspace_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"workspace_owner_avatar_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"workspace_owner_id": {
|
||||
"type": "string",
|
||||
"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
|
||||
// middleware is preferable to a join at the SQL layer so that we can keep the autogenerated
|
||||
// database types as they are.
|
||||
// OrganizationMember is the database object plus the Username and Avatar URL. Including these
|
||||
// in the middleware is preferable to a join at the SQL layer so that we can keep the
|
||||
// autogenerated database types as they are.
|
||||
type OrganizationMember struct {
|
||||
database.OrganizationMember
|
||||
Username string
|
||||
Username string
|
||||
AvatarURL string
|
||||
}
|
||||
|
||||
// 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{
|
||||
OrganizationMember: organizationMember,
|
||||
// Here we're making one exception 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 read the OrganizationMember, then we're explicitly saying here that they also
|
||||
// have permission to see the member's username, which is itself uncontroversial.
|
||||
// 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 and avatar URL.
|
||||
// If the caller has permission to read the OrganizationMember, then we're explicitly
|
||||
// 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
|
||||
// username in response to creating a workspace.
|
||||
Username: user.Username,
|
||||
// username in response to creating a workspace. Additionally, the frontend consumes
|
||||
// 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))
|
||||
})
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"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/dbtime"
|
||||
"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/testutil"
|
||||
)
|
||||
|
||||
func TestOrganizationParam(t *testing.T) {
|
||||
@ -139,6 +142,7 @@ func TestOrganizationParam(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var (
|
||||
ctx = testutil.Context(t, testutil.WaitShort)
|
||||
db = dbmem.New()
|
||||
rw = httptest.NewRecorder()
|
||||
r, user = setupAuthentication(db)
|
||||
@ -148,7 +152,14 @@ func TestOrganizationParam(t *testing.T) {
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
OrganizationID: organization.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("user", user.ID.String())
|
||||
rtr.Use(
|
||||
@ -161,9 +172,27 @@ func TestOrganizationParam(t *testing.T) {
|
||||
httpmw.ExtractOrganizationMemberParam(db),
|
||||
)
|
||||
rtr.Get("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
_ = httpmw.OrganizationParam(r)
|
||||
_ = httpmw.OrganizationMemberParam(r)
|
||||
org := httpmw.OrganizationParam(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)
|
||||
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)
|
||||
res := rw.Result()
|
||||
|
@ -1271,13 +1271,13 @@ func userOrganizationIDs(ctx context.Context, api *API, user database.User) ([]u
|
||||
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 {
|
||||
if id == user.ID {
|
||||
return user.Username, true
|
||||
return user, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
return database.User{}, false
|
||||
}
|
||||
|
||||
func convertAPIKey(k database.APIKey) codersdk.APIKey {
|
||||
|
@ -69,7 +69,7 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
return
|
||||
}
|
||||
ownerName, ok := usernameWithID(workspace.OwnerID, data.users)
|
||||
owner, ok := userByID(workspace.OwnerID, data.users)
|
||||
if !ok {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error converting workspace build.",
|
||||
@ -82,7 +82,8 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) {
|
||||
workspaceBuild,
|
||||
workspace,
|
||||
data.jobs[0],
|
||||
ownerName,
|
||||
owner.Username,
|
||||
owner.AvatarURL,
|
||||
data.resources,
|
||||
data.metadata,
|
||||
data.agents,
|
||||
@ -283,7 +284,7 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ
|
||||
})
|
||||
return
|
||||
}
|
||||
ownerName, ok := usernameWithID(workspace.OwnerID, data.users)
|
||||
owner, ok := userByID(workspace.OwnerID, data.users)
|
||||
if !ok {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error converting workspace build.",
|
||||
@ -296,7 +297,8 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ
|
||||
workspaceBuild,
|
||||
workspace,
|
||||
data.jobs[0],
|
||||
ownerName,
|
||||
owner.Username,
|
||||
owner.AvatarURL,
|
||||
data.resources,
|
||||
data.metadata,
|
||||
data.agents,
|
||||
@ -416,7 +418,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
return
|
||||
}
|
||||
ownerName, exists := usernameWithID(workspace.OwnerID, users)
|
||||
owner, exists := userByID(workspace.OwnerID, users)
|
||||
if !exists {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error converting workspace build.",
|
||||
@ -432,7 +434,8 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||
ProvisionerJob: *provisionerJob,
|
||||
QueuePosition: 0,
|
||||
},
|
||||
ownerName,
|
||||
owner.Username,
|
||||
owner.AvatarURL,
|
||||
[]database.WorkspaceResource{},
|
||||
[]database.WorkspaceResourceMetadatum{},
|
||||
[]database.WorkspaceAgent{},
|
||||
@ -833,7 +836,7 @@ func (api *API) convertWorkspaceBuilds(
|
||||
if !exists {
|
||||
return nil, xerrors.New("template version not found")
|
||||
}
|
||||
ownerName, exists := usernameWithID(workspace.OwnerID, users)
|
||||
owner, exists := userByID(workspace.OwnerID, users)
|
||||
if !exists {
|
||||
return nil, xerrors.Errorf("owner not found for workspace: %q", workspace.Name)
|
||||
}
|
||||
@ -842,7 +845,8 @@ func (api *API) convertWorkspaceBuilds(
|
||||
build,
|
||||
workspace,
|
||||
job,
|
||||
ownerName,
|
||||
owner.Username,
|
||||
owner.AvatarURL,
|
||||
workspaceResources,
|
||||
resourceMetadata,
|
||||
resourceAgents,
|
||||
@ -865,7 +869,7 @@ func (api *API) convertWorkspaceBuild(
|
||||
build database.WorkspaceBuild,
|
||||
workspace database.Workspace,
|
||||
job database.GetProvisionerJobsByIDsWithQueuePositionRow,
|
||||
ownerName string,
|
||||
username, avatarURL string,
|
||||
workspaceResources []database.WorkspaceResource,
|
||||
resourceMetadata []database.WorkspaceResourceMetadatum,
|
||||
resourceAgents []database.WorkspaceAgent,
|
||||
@ -909,7 +913,7 @@ func (api *API) convertWorkspaceBuild(
|
||||
scripts := scriptsByAgentID[agent.ID]
|
||||
logSources := logSourcesByAgentID[agent.ID]
|
||||
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(),
|
||||
)
|
||||
if err != nil {
|
||||
@ -923,26 +927,27 @@ func (api *API) convertWorkspaceBuild(
|
||||
apiJob := convertProvisionerJob(job)
|
||||
transition := codersdk.WorkspaceTransition(build.Transition)
|
||||
return codersdk.WorkspaceBuild{
|
||||
ID: build.ID,
|
||||
CreatedAt: build.CreatedAt,
|
||||
UpdatedAt: build.UpdatedAt,
|
||||
WorkspaceOwnerID: workspace.OwnerID,
|
||||
WorkspaceOwnerName: ownerName,
|
||||
WorkspaceID: build.WorkspaceID,
|
||||
WorkspaceName: workspace.Name,
|
||||
TemplateVersionID: build.TemplateVersionID,
|
||||
TemplateVersionName: templateVersion.Name,
|
||||
BuildNumber: build.BuildNumber,
|
||||
Transition: transition,
|
||||
InitiatorID: build.InitiatorID,
|
||||
InitiatorUsername: build.InitiatorByUsername,
|
||||
Job: apiJob,
|
||||
Deadline: codersdk.NewNullTime(build.Deadline, !build.Deadline.IsZero()),
|
||||
MaxDeadline: codersdk.NewNullTime(build.MaxDeadline, !build.MaxDeadline.IsZero()),
|
||||
Reason: codersdk.BuildReason(build.Reason),
|
||||
Resources: apiResources,
|
||||
Status: convertWorkspaceStatus(apiJob.Status, transition),
|
||||
DailyCost: build.DailyCost,
|
||||
ID: build.ID,
|
||||
CreatedAt: build.CreatedAt,
|
||||
UpdatedAt: build.UpdatedAt,
|
||||
WorkspaceOwnerID: workspace.OwnerID,
|
||||
WorkspaceOwnerName: username,
|
||||
WorkspaceOwnerAvatarURL: avatarURL,
|
||||
WorkspaceID: build.WorkspaceID,
|
||||
WorkspaceName: workspace.Name,
|
||||
TemplateVersionID: build.TemplateVersionID,
|
||||
TemplateVersionName: templateVersion.Name,
|
||||
BuildNumber: build.BuildNumber,
|
||||
Transition: transition,
|
||||
InitiatorID: build.InitiatorID,
|
||||
InitiatorUsername: build.InitiatorByUsername,
|
||||
Job: apiJob,
|
||||
Deadline: codersdk.NewNullTime(build.Deadline, !build.Deadline.IsZero()),
|
||||
MaxDeadline: codersdk.NewNullTime(build.MaxDeadline, !build.MaxDeadline.IsZero()),
|
||||
Reason: codersdk.BuildReason(build.Reason),
|
||||
Resources: apiResources,
|
||||
Status: convertWorkspaceStatus(apiJob.Status, transition),
|
||||
DailyCost: build.DailyCost,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/audit"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"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/rbac"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
@ -37,12 +38,23 @@ func TestWorkspaceBuild(t *testing.T) {
|
||||
propagation.Baggage{},
|
||||
),
|
||||
)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
auditor := audit.NewMock()
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
||||
IncludeProvisionerDaemon: true,
|
||||
Auditor: auditor,
|
||||
})
|
||||
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)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, 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[1].Ip.IPNet.IP.String(), "127.0.0.1")
|
||||
}, 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) {
|
||||
|
@ -94,7 +94,7 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Forbidden(rw)
|
||||
return
|
||||
}
|
||||
ownerName, ok := usernameWithID(workspace.OwnerID, data.users)
|
||||
owner, ok := userByID(workspace.OwnerID, data.users)
|
||||
if !ok {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching workspace resources.",
|
||||
@ -108,7 +108,8 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
|
||||
workspace,
|
||||
data.builds[0],
|
||||
data.templates[0],
|
||||
ownerName,
|
||||
owner.Username,
|
||||
owner.AvatarURL,
|
||||
api.Options.AllowWorkspaceRenames,
|
||||
)
|
||||
if err != nil {
|
||||
@ -281,7 +282,7 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
ownerName, ok := usernameWithID(workspace.OwnerID, data.users)
|
||||
owner, ok := userByID(workspace.OwnerID, data.users)
|
||||
if !ok {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching workspace resources.",
|
||||
@ -294,7 +295,8 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
|
||||
workspace,
|
||||
data.builds[0],
|
||||
data.templates[0],
|
||||
ownerName,
|
||||
owner.Username,
|
||||
owner.AvatarURL,
|
||||
api.Options.AllowWorkspaceRenames,
|
||||
)
|
||||
if err != nil {
|
||||
@ -591,6 +593,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
|
||||
QueuePosition: 0,
|
||||
},
|
||||
member.Username,
|
||||
member.AvatarURL,
|
||||
[]database.WorkspaceResource{},
|
||||
[]database.WorkspaceResourceMetadatum{},
|
||||
[]database.WorkspaceAgent{},
|
||||
@ -613,6 +616,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
|
||||
apiBuild,
|
||||
template,
|
||||
member.Username,
|
||||
member.AvatarURL,
|
||||
api.Options.AllowWorkspaceRenames,
|
||||
)
|
||||
if err != nil {
|
||||
@ -941,7 +945,7 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
return
|
||||
}
|
||||
ownerName, ok := usernameWithID(workspace.OwnerID, data.users)
|
||||
owner, ok := userByID(workspace.OwnerID, data.users)
|
||||
if !ok {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching workspace resources.",
|
||||
@ -962,7 +966,8 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) {
|
||||
workspace,
|
||||
data.builds[0],
|
||||
data.templates[0],
|
||||
ownerName,
|
||||
owner.Username,
|
||||
owner.AvatarURL,
|
||||
api.Options.AllowWorkspaceRenames,
|
||||
)
|
||||
if err != nil {
|
||||
@ -1372,7 +1377,7 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ownerName, ok := usernameWithID(workspace.OwnerID, data.users)
|
||||
owner, ok := userByID(workspace.OwnerID, data.users)
|
||||
if !ok {
|
||||
_ = sendEvent(ctx, codersdk.ServerSentEvent{
|
||||
Type: codersdk.ServerSentEventTypeError,
|
||||
@ -1389,7 +1394,8 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) {
|
||||
workspace,
|
||||
data.builds[0],
|
||||
data.templates[0],
|
||||
ownerName,
|
||||
owner.Username,
|
||||
owner.AvatarURL,
|
||||
api.Options.AllowWorkspaceRenames,
|
||||
)
|
||||
if err != nil {
|
||||
@ -1556,6 +1562,7 @@ func convertWorkspaces(requesterID uuid.UUID, workspaces []database.Workspace, d
|
||||
build,
|
||||
template,
|
||||
owner.Username,
|
||||
owner.AvatarURL,
|
||||
data.allowRenames,
|
||||
)
|
||||
if err != nil {
|
||||
@ -1572,7 +1579,8 @@ func convertWorkspace(
|
||||
workspace database.Workspace,
|
||||
workspaceBuild codersdk.WorkspaceBuild,
|
||||
template database.Template,
|
||||
ownerName string,
|
||||
username string,
|
||||
avatarURL string,
|
||||
allowRenames bool,
|
||||
) (codersdk.Workspace, error) {
|
||||
if requesterID == uuid.Nil {
|
||||
@ -1612,7 +1620,8 @@ func convertWorkspace(
|
||||
CreatedAt: workspace.CreatedAt,
|
||||
UpdatedAt: workspace.UpdatedAt,
|
||||
OwnerID: workspace.OwnerID,
|
||||
OwnerName: ownerName,
|
||||
OwnerName: username,
|
||||
OwnerAvatarURL: avatarURL,
|
||||
OrganizationID: workspace.OrganizationID,
|
||||
TemplateID: workspace.TemplateID,
|
||||
LatestBuild: workspaceBuild,
|
||||
|
Reference in New Issue
Block a user