feat: add sharing info to /workspaces endpoint (#21049)

closes: https://github.com/coder/internal/issues/858

Similar to https://github.com/coder/coder/pull/19375, this one uses
system permissions for fetching actual user and group data.

Modifies the `workspaces_expanded` view to fetch the required data; this way it's made available to all code paths that make use of it.  

Also fixes a bug in a test helper function that can result in `null` being saved to the DB for `user_acl` or `group_acl` and break tests; a defensive check constraint that prevents this is worth a PR, e.g:

`ALTER TABLE workspaces
   ADD CONSTRAINT group_acl_is_object CHECK (jsonb_typeof(group_acl) = 'object');`

Also adds missing  `OwnerName` in `ConvertWorkspaceRows`.
This commit is contained in:
George K
2025-12-15 08:42:08 -08:00
committed by GitHub
parent 7ecfd1aa07
commit 103967ed02
22 changed files with 838 additions and 92 deletions
+10 -2
View File
@@ -440,10 +440,18 @@ func Workspace(t testing.TB, db database.Store, orig database.WorkspaceTable) da
workspace.DormantAt = orig.DormantAt
}
if len(orig.UserACL) > 0 || len(orig.GroupACL) > 0 {
userACL := orig.UserACL
if userACL == nil {
userACL = database.WorkspaceACL{}
}
groupACL := orig.GroupACL
if groupACL == nil {
groupACL = database.WorkspaceACL{}
}
err = db.UpdateWorkspaceACLByID(genCtx, database.UpdateWorkspaceACLByIDParams{
ID: workspace.ID,
UserACL: orig.UserACL,
GroupACL: orig.GroupACL,
UserACL: userACL,
GroupACL: groupACL,
})
require.NoError(t, err, "set workspace ACL")
workspace.UserACL = orig.UserACL
+7 -1
View File
@@ -2938,7 +2938,13 @@ CREATE VIEW workspaces_expanded AS
templates.display_name AS template_display_name,
templates.icon AS template_icon,
templates.description AS template_description,
tasks.id AS task_id
tasks.id AS task_id,
COALESCE(( SELECT jsonb_object_agg(acl.key, jsonb_build_object('name', COALESCE(g.name, ''::text), 'avatar_url', COALESCE(g.avatar_url, ''::text))) AS jsonb_object_agg
FROM (jsonb_each(workspaces.group_acl) acl(key, value)
LEFT JOIN groups g ON ((g.id = (acl.key)::uuid)))), '{}'::jsonb) AS group_acl_display_info,
COALESCE(( SELECT jsonb_object_agg(acl.key, jsonb_build_object('name', COALESCE(vu.name, ''::text), 'avatar_url', COALESCE(vu.avatar_url, ''::text))) AS jsonb_object_agg
FROM (jsonb_each(workspaces.user_acl) acl(key, value)
LEFT JOIN visible_users vu ON ((vu.id = (acl.key)::uuid)))), '{}'::jsonb) AS user_acl_display_info
FROM ((((workspaces
JOIN visible_users ON ((workspaces.owner_id = visible_users.id)))
JOIN organizations ON ((workspaces.organization_id = organizations.id)))
@@ -0,0 +1,41 @@
DROP VIEW workspaces_expanded;
-- Revert to passing through raw user_acl and group_acl columns.
CREATE VIEW workspaces_expanded AS
SELECT workspaces.id,
workspaces.created_at,
workspaces.updated_at,
workspaces.owner_id,
workspaces.organization_id,
workspaces.template_id,
workspaces.deleted,
workspaces.name,
workspaces.autostart_schedule,
workspaces.ttl,
workspaces.last_used_at,
workspaces.dormant_at,
workspaces.deleting_at,
workspaces.automatic_updates,
workspaces.favorite,
workspaces.next_start_at,
workspaces.group_acl,
workspaces.user_acl,
visible_users.avatar_url AS owner_avatar_url,
visible_users.username AS owner_username,
visible_users.name AS owner_name,
organizations.name AS organization_name,
organizations.display_name AS organization_display_name,
organizations.icon AS organization_icon,
organizations.description AS organization_description,
templates.name AS template_name,
templates.display_name AS template_display_name,
templates.icon AS template_icon,
templates.description AS template_description,
tasks.id AS task_id
FROM ((((workspaces
JOIN visible_users ON ((workspaces.owner_id = visible_users.id)))
JOIN organizations ON ((workspaces.organization_id = organizations.id)))
JOIN templates ON ((workspaces.template_id = templates.id)))
LEFT JOIN tasks ON ((workspaces.id = tasks.workspace_id)));
COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.';
@@ -0,0 +1,65 @@
DROP VIEW workspaces_expanded;
-- Expand more by including group_acl_display_info and
-- user_acl_display_info columns with the actors' name and avatar.
CREATE VIEW workspaces_expanded AS
SELECT workspaces.id,
workspaces.created_at,
workspaces.updated_at,
workspaces.owner_id,
workspaces.organization_id,
workspaces.template_id,
workspaces.deleted,
workspaces.name,
workspaces.autostart_schedule,
workspaces.ttl,
workspaces.last_used_at,
workspaces.dormant_at,
workspaces.deleting_at,
workspaces.automatic_updates,
workspaces.favorite,
workspaces.next_start_at,
workspaces.group_acl,
workspaces.user_acl,
visible_users.avatar_url AS owner_avatar_url,
visible_users.username AS owner_username,
visible_users.name AS owner_name,
organizations.name AS organization_name,
organizations.display_name AS organization_display_name,
organizations.icon AS organization_icon,
organizations.description AS organization_description,
templates.name AS template_name,
templates.display_name AS template_display_name,
templates.icon AS template_icon,
templates.description AS template_description,
tasks.id AS task_id,
-- Workspace ACL actors' display info
COALESCE((
SELECT jsonb_object_agg(
acl.key,
jsonb_build_object(
'name', COALESCE(g.name, ''),
'avatar_url', COALESCE(g.avatar_url, '')
)
)
FROM jsonb_each(workspaces.group_acl) AS acl
LEFT JOIN groups g ON g.id = acl.key::uuid
), '{}'::jsonb) AS group_acl_display_info,
COALESCE((
SELECT jsonb_object_agg(
acl.key,
jsonb_build_object(
'name', COALESCE(vu.name, ''),
'avatar_url', COALESCE(vu.avatar_url, '')
)
)
FROM jsonb_each(workspaces.user_acl) AS acl
LEFT JOIN visible_users vu ON vu.id = acl.key::uuid
), '{}'::jsonb) AS user_acl_display_info
FROM ((((workspaces
JOIN visible_users ON ((workspaces.owner_id = visible_users.id)))
JOIN organizations ON ((workspaces.organization_id = organizations.id)))
JOIN templates ON ((workspaces.template_id = templates.id)))
LEFT JOIN tasks ON ((workspaces.id = tasks.workspace_id)));
COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.';
+25 -2
View File
@@ -658,7 +658,7 @@ func ConvertUserRows(rows []GetUsersRow) []User {
return users
}
func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace {
func ConvertWorkspaceRows(rows []GetWorkspacesRow) ([]Workspace, error) {
workspaces := make([]Workspace, len(rows))
for i, r := range rows {
workspaces[i] = Workspace{
@@ -679,6 +679,7 @@ func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace {
Favorite: r.Favorite,
OwnerAvatarUrl: r.OwnerAvatarUrl,
OwnerUsername: r.OwnerUsername,
OwnerName: r.OwnerName,
OrganizationName: r.OrganizationName,
OrganizationDisplayName: r.OrganizationDisplayName,
OrganizationIcon: r.OrganizationIcon,
@@ -690,9 +691,31 @@ func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace {
NextStartAt: r.NextStartAt,
TaskID: r.TaskID,
}
var err error
err = workspaces[i].UserACL.Scan(r.UserACL)
if err != nil {
return nil, xerrors.Errorf("scan user ACL %q: %w", r.UserACL, err)
}
err = workspaces[i].GroupACL.Scan(r.GroupACL)
if err != nil {
return nil, xerrors.Errorf("scan group ACL %q: %w", r.GroupACL, err)
}
err = workspaces[i].UserACLDisplayInfo.Scan(r.UserACLDisplayInfo)
if err != nil {
return nil, xerrors.Errorf("scan user ACL display info %q: %w",
r.UserACLDisplayInfo, err)
}
err = workspaces[i].GroupACLDisplayInfo.Scan(r.GroupACLDisplayInfo)
if err != nil {
return nil, xerrors.Errorf("scan group ACL display info %q: %w",
r.GroupACLDisplayInfo, err)
}
}
return workspaces
return workspaces, nil
}
func (g Group) IsEveryone() bool {
+2
View File
@@ -323,6 +323,8 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
&i.TemplateIcon,
&i.TemplateDescription,
&i.TaskID,
&i.GroupACLDisplayInfo,
&i.UserACLDisplayInfo,
&i.TemplateVersionID,
&i.TemplateVersionName,
&i.LatestBuildCompletedAt,
+32 -30
View File
@@ -4645,36 +4645,38 @@ type WebpushSubscription struct {
// Joins in the display name information such as username, avatar, and organization name.
type Workspace struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
Deleted bool `db:"deleted" json:"deleted"`
Name string `db:"name" json:"name"`
AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"`
Ttl sql.NullInt64 `db:"ttl" json:"ttl"`
LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"`
DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"`
DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"`
AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"`
Favorite bool `db:"favorite" json:"favorite"`
NextStartAt sql.NullTime `db:"next_start_at" json:"next_start_at"`
GroupACL WorkspaceACL `db:"group_acl" json:"group_acl"`
UserACL WorkspaceACL `db:"user_acl" json:"user_acl"`
OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"`
OwnerUsername string `db:"owner_username" json:"owner_username"`
OwnerName string `db:"owner_name" json:"owner_name"`
OrganizationName string `db:"organization_name" json:"organization_name"`
OrganizationDisplayName string `db:"organization_display_name" json:"organization_display_name"`
OrganizationIcon string `db:"organization_icon" json:"organization_icon"`
OrganizationDescription string `db:"organization_description" json:"organization_description"`
TemplateName string `db:"template_name" json:"template_name"`
TemplateDisplayName string `db:"template_display_name" json:"template_display_name"`
TemplateIcon string `db:"template_icon" json:"template_icon"`
TemplateDescription string `db:"template_description" json:"template_description"`
TaskID uuid.NullUUID `db:"task_id" json:"task_id"`
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
Deleted bool `db:"deleted" json:"deleted"`
Name string `db:"name" json:"name"`
AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"`
Ttl sql.NullInt64 `db:"ttl" json:"ttl"`
LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"`
DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"`
DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"`
AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"`
Favorite bool `db:"favorite" json:"favorite"`
NextStartAt sql.NullTime `db:"next_start_at" json:"next_start_at"`
GroupACL WorkspaceACL `db:"group_acl" json:"group_acl"`
UserACL WorkspaceACL `db:"user_acl" json:"user_acl"`
OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"`
OwnerUsername string `db:"owner_username" json:"owner_username"`
OwnerName string `db:"owner_name" json:"owner_name"`
OrganizationName string `db:"organization_name" json:"organization_name"`
OrganizationDisplayName string `db:"organization_display_name" json:"organization_display_name"`
OrganizationIcon string `db:"organization_icon" json:"organization_icon"`
OrganizationDescription string `db:"organization_description" json:"organization_description"`
TemplateName string `db:"template_name" json:"template_name"`
TemplateDisplayName string `db:"template_display_name" json:"template_display_name"`
TemplateIcon string `db:"template_icon" json:"template_icon"`
TemplateDescription string `db:"template_description" json:"template_description"`
TaskID uuid.NullUUID `db:"task_id" json:"task_id"`
GroupACLDisplayInfo WorkspaceACLDisplayInfo `db:"group_acl_display_info" json:"group_acl_display_info"`
UserACLDisplayInfo WorkspaceACLDisplayInfo `db:"user_acl_display_info" json:"user_acl_display_info"`
}
type WorkspaceAgent struct {
+25 -9
View File
@@ -22295,7 +22295,7 @@ func (q *sqlQuerier) GetWorkspaceACLByID(ctx context.Context, id uuid.UUID) (Get
const getWorkspaceByAgentID = `-- name: GetWorkspaceByAgentID :one
SELECT
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id, group_acl_display_info, user_acl_display_info
FROM
workspaces_expanded as workspaces
WHERE
@@ -22357,13 +22357,15 @@ func (q *sqlQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUI
&i.TemplateIcon,
&i.TemplateDescription,
&i.TaskID,
&i.GroupACLDisplayInfo,
&i.UserACLDisplayInfo,
)
return i, err
}
const getWorkspaceByID = `-- name: GetWorkspaceByID :one
SELECT
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id, group_acl_display_info, user_acl_display_info
FROM
workspaces_expanded
WHERE
@@ -22406,13 +22408,15 @@ func (q *sqlQuerier) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Worksp
&i.TemplateIcon,
&i.TemplateDescription,
&i.TaskID,
&i.GroupACLDisplayInfo,
&i.UserACLDisplayInfo,
)
return i, err
}
const getWorkspaceByOwnerIDAndName = `-- name: GetWorkspaceByOwnerIDAndName :one
SELECT
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id, group_acl_display_info, user_acl_display_info
FROM
workspaces_expanded as workspaces
WHERE
@@ -22462,13 +22466,15 @@ func (q *sqlQuerier) GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWo
&i.TemplateIcon,
&i.TemplateDescription,
&i.TaskID,
&i.GroupACLDisplayInfo,
&i.UserACLDisplayInfo,
)
return i, err
}
const getWorkspaceByResourceID = `-- name: GetWorkspaceByResourceID :one
SELECT
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id, group_acl_display_info, user_acl_display_info
FROM
workspaces_expanded as workspaces
WHERE
@@ -22525,13 +22531,15 @@ func (q *sqlQuerier) GetWorkspaceByResourceID(ctx context.Context, resourceID uu
&i.TemplateIcon,
&i.TemplateDescription,
&i.TaskID,
&i.GroupACLDisplayInfo,
&i.UserACLDisplayInfo,
)
return i, err
}
const getWorkspaceByWorkspaceAppID = `-- name: GetWorkspaceByWorkspaceAppID :one
SELECT
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id
id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id, group_acl_display_info, user_acl_display_info
FROM
workspaces_expanded as workspaces
WHERE
@@ -22600,6 +22608,8 @@ func (q *sqlQuerier) GetWorkspaceByWorkspaceAppID(ctx context.Context, workspace
&i.TemplateIcon,
&i.TemplateDescription,
&i.TaskID,
&i.GroupACLDisplayInfo,
&i.UserACLDisplayInfo,
)
return i, err
}
@@ -22649,7 +22659,7 @@ SELECT
),
filtered_workspaces AS (
SELECT
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl, workspaces.owner_avatar_url, workspaces.owner_username, workspaces.owner_name, workspaces.organization_name, workspaces.organization_display_name, workspaces.organization_icon, workspaces.organization_description, workspaces.template_name, workspaces.template_display_name, workspaces.template_icon, workspaces.template_description, workspaces.task_id,
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl, workspaces.owner_avatar_url, workspaces.owner_username, workspaces.owner_name, workspaces.organization_name, workspaces.organization_display_name, workspaces.organization_icon, workspaces.organization_description, workspaces.template_name, workspaces.template_display_name, workspaces.template_icon, workspaces.template_description, workspaces.task_id, workspaces.group_acl_display_info, workspaces.user_acl_display_info,
latest_build.template_version_id,
latest_build.template_version_name,
latest_build.completed_at as latest_build_completed_at,
@@ -22933,7 +22943,7 @@ WHERE
-- @authorize_filter
), filtered_workspaces_order AS (
SELECT
fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.task_id, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_external_agent
fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.task_id, fw.group_acl_display_info, fw.user_acl_display_info, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_external_agent
FROM
filtered_workspaces fw
ORDER BY
@@ -22954,7 +22964,7 @@ WHERE
$25
), filtered_workspaces_order_with_summary AS (
SELECT
fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.task_id, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_external_agent
fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.task_id, fwo.group_acl_display_info, fwo.user_acl_display_info, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_external_agent
FROM
filtered_workspaces_order fwo
-- Return a technical summary row with total count of workspaces.
@@ -22991,6 +23001,8 @@ WHERE
'', -- template_icon
'', -- template_description
'00000000-0000-0000-0000-000000000000'::uuid, -- task_id
'{}'::jsonb, -- group_acl_display_info
'{}'::jsonb, -- user_acl_display_info
-- Extra columns added to ` + "`" + `filtered_workspaces` + "`" + `
'00000000-0000-0000-0000-000000000000'::uuid, -- template_version_id
'', -- template_version_name
@@ -23009,7 +23021,7 @@ WHERE
filtered_workspaces
)
SELECT
fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.task_id, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_external_agent,
fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.task_id, fwos.group_acl_display_info, fwos.user_acl_display_info, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_external_agent,
tc.count
FROM
filtered_workspaces_order_with_summary fwos
@@ -23078,6 +23090,8 @@ type GetWorkspacesRow struct {
TemplateIcon string `db:"template_icon" json:"template_icon"`
TemplateDescription string `db:"template_description" json:"template_description"`
TaskID uuid.NullUUID `db:"task_id" json:"task_id"`
GroupACLDisplayInfo interface{} `db:"group_acl_display_info" json:"group_acl_display_info"`
UserACLDisplayInfo interface{} `db:"user_acl_display_info" json:"user_acl_display_info"`
TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"`
TemplateVersionName sql.NullString `db:"template_version_name" json:"template_version_name"`
LatestBuildCompletedAt sql.NullTime `db:"latest_build_completed_at" json:"latest_build_completed_at"`
@@ -23160,6 +23174,8 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams)
&i.TemplateIcon,
&i.TemplateDescription,
&i.TaskID,
&i.GroupACLDisplayInfo,
&i.UserACLDisplayInfo,
&i.TemplateVersionID,
&i.TemplateVersionName,
&i.LatestBuildCompletedAt,
+2
View File
@@ -451,6 +451,8 @@ WHERE
'', -- template_icon
'', -- template_description
'00000000-0000-0000-0000-000000000000'::uuid, -- task_id
'{}'::jsonb, -- group_acl_display_info
'{}'::jsonb, -- user_acl_display_info
-- Extra columns added to `filtered_workspaces`
'00000000-0000-0000-0000-000000000000'::uuid, -- template_version_id
'', -- template_version_name
+8
View File
@@ -91,6 +91,12 @@ sql:
- column: "workspaces_expanded.group_acl"
go_type:
type: "WorkspaceACL"
- column: "workspaces_expanded.user_acl_display_info"
go_type:
type: "WorkspaceACLDisplayInfo"
- column: "workspaces_expanded.group_acl_display_info"
go_type:
type: "WorkspaceACLDisplayInfo"
- column: "notification_templates.actions"
go_type:
type: "[]byte"
@@ -159,6 +165,8 @@ sql:
jwt: JWT
user_acl: UserACL
group_acl: GroupACL
user_acl_display_info: UserACLDisplayInfo
group_acl_display_info: GroupACLDisplayInfo
troubleshooting_url: TroubleshootingURL
default_ttl: DefaultTTL
motd_file: MOTDFile
+29 -6
View File
@@ -67,9 +67,10 @@ func (t *TemplateACL) Scan(src interface{}) error {
switch v := src.(type) {
case string:
return json.Unmarshal([]byte(v), &t)
case []byte, json.RawMessage:
//nolint
return json.Unmarshal(v.([]byte), &t)
case []byte:
return json.Unmarshal(v, &t)
case json.RawMessage:
return json.Unmarshal(v, &t)
}
return xerrors.Errorf("unexpected type %T", src)
@@ -85,9 +86,10 @@ func (t *WorkspaceACL) Scan(src interface{}) error {
switch v := src.(type) {
case string:
return json.Unmarshal([]byte(v), &t)
case []byte, json.RawMessage:
//nolint
return json.Unmarshal(v.([]byte), &t)
case []byte:
return json.Unmarshal(v, &t)
case json.RawMessage:
return json.Unmarshal(v, &t)
}
return xerrors.Errorf("unexpected type %T", src)
@@ -112,6 +114,27 @@ type WorkspaceACLEntry struct {
Permissions []policy.Action `json:"permissions"`
}
// WorkspaceACLDisplayInfo supplements workspace ACLs with the actors'
// display info. Key is string rather than uuid.UUID as this aligns
// with how RBAC represents actor IDs.
type WorkspaceACLDisplayInfo map[string]struct {
Name string `json:"name"`
AvatarURL string `json:"avatar_url"`
}
// WorkspaceACLDisplayInfo is only used to read from the DB.
func (w *WorkspaceACLDisplayInfo) Scan(src interface{}) error {
switch v := src.(type) {
case string:
return json.Unmarshal([]byte(v), w)
case []byte:
return json.Unmarshal(v, w)
case json.RawMessage:
return json.Unmarshal(v, w)
}
return xerrors.Errorf("unexpected type %T", src)
}
type ExternalAuthProvider struct {
ID string `json:"id"`
Optional bool `json:"optional,omitempty"`