fix!: omit name, avatar_url and last_seen_at from responses when empty (#18005)

User name, avatar URL, and last seen at, are not required fields so they
can be empty. Instead of returning the 0 values from Go, we want to make
it more agnostic, and omit them when they are empty. This make the docs
and usage way clearer for consumers.
This commit is contained in:
Bruno Quaresma
2025-05-23 11:35:05 -03:00
committed by GitHub
parent 96f69b8e13
commit 94c129c03d
11 changed files with 21 additions and 25 deletions

View File

@ -24,7 +24,6 @@
"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,

View File

@ -2,7 +2,6 @@
{ {
"id": "==========[first user ID]===========", "id": "==========[first user ID]===========",
"username": "testuser", "username": "testuser",
"avatar_url": "",
"name": "Test User", "name": "Test User",
"email": "testuser@coder.com", "email": "testuser@coder.com",
"created_at": "====[timestamp]=====", "created_at": "====[timestamp]=====",
@ -23,8 +22,6 @@
{ {
"id": "==========[second user ID]==========", "id": "==========[second user ID]==========",
"username": "testuser2", "username": "testuser2",
"avatar_url": "",
"name": "",
"email": "testuser2@coder.com", "email": "testuser2@coder.com",
"created_at": "====[timestamp]=====", "created_at": "====[timestamp]=====",
"updated_at": "====[timestamp]=====", "updated_at": "====[timestamp]=====",

View File

@ -74,8 +74,8 @@ type OrganizationMember struct {
type OrganizationMemberWithUserData struct { type OrganizationMemberWithUserData struct {
Username string `table:"username,default_sort" json:"username"` Username string `table:"username,default_sort" json:"username"`
Name string `table:"name" json:"name"` Name string `table:"name" json:"name,omitempty"`
AvatarURL string `json:"avatar_url"` AvatarURL string `json:"avatar_url,omitempty"`
Email string `json:"email"` Email string `json:"email"`
GlobalRoles []SlimRole `json:"global_roles"` GlobalRoles []SlimRole `json:"global_roles"`
OrganizationMember `table:"m,recursive_inline"` OrganizationMember `table:"m,recursive_inline"`

View File

@ -40,7 +40,7 @@ type UsersRequest struct {
type MinimalUser struct { type MinimalUser struct {
ID uuid.UUID `json:"id" validate:"required" table:"id" format:"uuid"` ID uuid.UUID `json:"id" validate:"required" table:"id" format:"uuid"`
Username string `json:"username" validate:"required" table:"username,default_sort"` Username string `json:"username" validate:"required" table:"username,default_sort"`
AvatarURL string `json:"avatar_url" format:"uri"` AvatarURL string `json:"avatar_url,omitempty" format:"uri"`
} }
// ReducedUser omits role and organization information. Roles are deduced from // ReducedUser omits role and organization information. Roles are deduced from
@ -49,11 +49,11 @@ type MinimalUser struct {
// required by the frontend. // required by the frontend.
type ReducedUser struct { type ReducedUser struct {
MinimalUser `table:"m,recursive_inline"` MinimalUser `table:"m,recursive_inline"`
Name string `json:"name"` Name string `json:"name,omitempty"`
Email string `json:"email" validate:"required" table:"email" format:"email"` Email string `json:"email" validate:"required" table:"email" format:"email"`
CreatedAt time.Time `json:"created_at" validate:"required" table:"created at" format:"date-time"` CreatedAt time.Time `json:"created_at" validate:"required" table:"created at" format:"date-time"`
UpdatedAt time.Time `json:"updated_at" table:"updated at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" table:"updated at" format:"date-time"`
LastSeenAt time.Time `json:"last_seen_at" format:"date-time"` LastSeenAt time.Time `json:"last_seen_at,omitempty" format:"date-time"`
Status UserStatus `json:"status" table:"status" enums:"active,suspended"` Status UserStatus `json:"status" table:"status" enums:"active,suspended"`
LoginType LoginType `json:"login_type"` LoginType LoginType `json:"login_type"`

View File

@ -58,7 +58,7 @@ type WorkspaceBuild struct {
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"`
WorkspaceOwnerAvatarURL string `json:"workspace_owner_avatar_url"` WorkspaceOwnerAvatarURL string `json:"workspace_owner_avatar_url,omitempty"`
TemplateVersionID uuid.UUID `json:"template_version_id" format:"uuid"` TemplateVersionID uuid.UUID `json:"template_version_id" format:"uuid"`
TemplateVersionName string `json:"template_version_name"` TemplateVersionName string `json:"template_version_name"`
BuildNumber int32 `json:"build_number"` BuildNumber int32 `json:"build_number"`

View File

@ -1366,7 +1366,7 @@ export interface MinimalOrganization {
export interface MinimalUser { export interface MinimalUser {
readonly id: string; readonly id: string;
readonly username: string; readonly username: string;
readonly avatar_url: string; readonly avatar_url?: string;
} }
// From netcheck/netcheck.go // From netcheck/netcheck.go
@ -1639,8 +1639,8 @@ export interface OrganizationMember {
// From codersdk/organizations.go // From codersdk/organizations.go
export interface OrganizationMemberWithUserData extends OrganizationMember { export interface OrganizationMemberWithUserData extends OrganizationMember {
readonly username: string; readonly username: string;
readonly name: string; readonly name?: string;
readonly avatar_url: string; readonly avatar_url?: string;
readonly email: string; readonly email: string;
readonly global_roles: readonly SlimRole[]; readonly global_roles: readonly SlimRole[];
} }
@ -2253,11 +2253,11 @@ export interface RateLimitConfig {
// From codersdk/users.go // From codersdk/users.go
export interface ReducedUser extends MinimalUser { export interface ReducedUser extends MinimalUser {
readonly name: string; readonly name?: string;
readonly email: string; readonly email: string;
readonly created_at: string; readonly created_at: string;
readonly updated_at: string; readonly updated_at: string;
readonly last_seen_at: string; readonly last_seen_at?: string;
readonly status: UserStatus; readonly status: UserStatus;
readonly login_type: LoginType; readonly login_type: LoginType;
readonly theme_preference?: string; readonly theme_preference?: string;
@ -3609,7 +3609,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 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;

View File

@ -20,7 +20,7 @@ import { prepareQuery } from "utils/filters";
// The common properties between users and org members that we need. // The common properties between users and org members that we need.
export type SelectedUser = { export type SelectedUser = {
avatar_url: string; avatar_url?: string;
email: string; email: string;
username: string; username: string;
}; };

View File

@ -62,7 +62,7 @@ const ChatLanding: FC = () => {
textAlign: "center", textAlign: "center",
}} }}
> >
Good evening, {user?.name.split(" ")[0]} Good evening, {(user.name ?? user.username).split(" ")[0]}
</h1> </h1>
<p <p
css={{ css={{

View File

@ -13,7 +13,7 @@ describe("AccountForm", () => {
// Given // Given
const mockInitialValues: UpdateUserProfileRequest = { const mockInitialValues: UpdateUserProfileRequest = {
username: MockUserMember.username, username: MockUserMember.username,
name: MockUserMember.name, name: MockUserMember.name ?? MockUserMember.username,
}; };
// When // When
@ -44,7 +44,7 @@ describe("AccountForm", () => {
// Given // Given
const mockInitialValues: UpdateUserProfileRequest = { const mockInitialValues: UpdateUserProfileRequest = {
username: MockUserMember.username, username: MockUserMember.username,
name: MockUserMember.name, name: MockUserMember.name ?? MockUserMember.username,
}; };
// When // When

View File

@ -29,7 +29,7 @@ const AccountPage: FC = () => {
email={me.email} email={me.email}
updateProfileError={updateProfileError} updateProfileError={updateProfileError}
isLoading={isUpdatingProfile} isLoading={isUpdatingProfile}
initialValues={{ username: me.username, name: me.name }} initialValues={{ username: me.username, name: me.name ?? "" }}
onSubmit={updateProfile} onSubmit={updateProfile}
/> />
</Section> </Section>

View File

@ -539,8 +539,8 @@ export const MockOrganizationMember: TypesGen.OrganizationMemberWithUserData = {
user_id: MockUserOwner.id, user_id: MockUserOwner.id,
username: MockUserOwner.username, username: MockUserOwner.username,
email: MockUserOwner.email, email: MockUserOwner.email,
created_at: "", updated_at: "2025-05-22T17:51:49.49745Z",
updated_at: "", created_at: "2025-05-22T17:51:49.497449Z",
name: MockUserOwner.name, name: MockUserOwner.name,
avatar_url: MockUserOwner.avatar_url, avatar_url: MockUserOwner.avatar_url,
global_roles: MockUserOwner.roles, global_roles: MockUserOwner.roles,
@ -553,8 +553,8 @@ export const MockOrganizationMember2: TypesGen.OrganizationMemberWithUserData =
user_id: MockUserMember.id, user_id: MockUserMember.id,
username: MockUserMember.username, username: MockUserMember.username,
email: MockUserMember.email, email: MockUserMember.email,
created_at: "", updated_at: "2025-05-22T17:51:49.49745Z",
updated_at: "", created_at: "2025-05-22T17:51:49.497449Z",
name: MockUserMember.name, name: MockUserMember.name,
avatar_url: MockUserMember.avatar_url, avatar_url: MockUserMember.avatar_url,
global_roles: MockUserMember.roles, global_roles: MockUserMember.roles,