mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
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:
1
cli/testdata/coder_list_--output_json.golden
vendored
1
cli/testdata/coder_list_--output_json.golden
vendored
@ -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,
|
||||||
|
@ -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]=====",
|
||||||
|
@ -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"`
|
||||||
|
@ -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"`
|
||||||
|
@ -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"`
|
||||||
|
12
site/src/api/typesGenerated.ts
generated
12
site/src/api/typesGenerated.ts
generated
@ -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;
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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={{
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user