feat: show template.display_name on Workspace pages (#5082)

* feat: expose template.display_name via Workspaces endpoint

* Fix: MockWorkspace

* UI: Workspace stats and row

* Show template.display_name on pages

* Fix: address PR comments

* Add helper function: getDisplayWorkspaceTemplateName
This commit is contained in:
Marcin Tojek
2022-11-16 15:50:32 +01:00
committed by GitHub
parent c1ecc91aab
commit 32927b1a24
10 changed files with 106 additions and 32 deletions
+15 -14
View File
@@ -1011,20 +1011,21 @@ func convertWorkspace(
ttlMillis := convertWorkspaceTTLMillis(workspace.Ttl)
return codersdk.Workspace{
ID: workspace.ID,
CreatedAt: workspace.CreatedAt,
UpdatedAt: workspace.UpdatedAt,
OwnerID: workspace.OwnerID,
OwnerName: owner.Username,
TemplateID: workspace.TemplateID,
LatestBuild: workspaceBuild,
TemplateName: template.Name,
TemplateIcon: template.Icon,
Outdated: workspaceBuild.TemplateVersionID.String() != template.ActiveVersionID.String(),
Name: workspace.Name,
AutostartSchedule: autostartSchedule,
TTLMillis: ttlMillis,
LastUsedAt: workspace.LastUsedAt,
ID: workspace.ID,
CreatedAt: workspace.CreatedAt,
UpdatedAt: workspace.UpdatedAt,
OwnerID: workspace.OwnerID,
OwnerName: owner.Username,
TemplateID: workspace.TemplateID,
LatestBuild: workspaceBuild,
TemplateName: template.Name,
TemplateIcon: template.Icon,
TemplateDisplayName: template.DisplayName,
Outdated: workspaceBuild.TemplateVersionID.String() != template.ActiveVersionID.String(),
Name: workspace.Name,
AutostartSchedule: autostartSchedule,
TTLMillis: ttlMillis,
LastUsedAt: workspace.LastUsedAt,
}
}
+30
View File
@@ -115,6 +115,36 @@ func TestWorkspace(t *testing.T) {
})
require.Error(t, err, "workspace rename should have failed")
})
t.Run("TemplateProperties", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
const templateIcon = "/img/icon.svg"
const templateDisplayName = "This is template"
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.Icon = templateIcon
ctr.DisplayName = templateDisplayName
})
require.NotEmpty(t, template.Name)
require.NotEmpty(t, template.DisplayName)
require.NotEmpty(t, template.Icon)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
ws, err := client.Workspace(ctx, workspace.ID)
require.NoError(t, err)
assert.Equal(t, user.UserID, ws.LatestBuild.InitiatorID)
assert.Equal(t, codersdk.BuildReasonInitiator, ws.LatestBuild.Reason)
assert.Equal(t, template.Name, ws.TemplateName)
assert.Equal(t, templateIcon, ws.TemplateIcon)
assert.Equal(t, templateDisplayName, ws.TemplateDisplayName)
})
}
func TestAdminViewAllWorkspaces(t *testing.T) {
+15 -14
View File
@@ -17,20 +17,21 @@ import (
// Workspace is a deployment of a template. It references a specific
// version and can be updated.
type Workspace struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
OwnerID uuid.UUID `json:"owner_id"`
OwnerName string `json:"owner_name"`
TemplateID uuid.UUID `json:"template_id"`
TemplateName string `json:"template_name"`
TemplateIcon string `json:"template_icon"`
LatestBuild WorkspaceBuild `json:"latest_build"`
Outdated bool `json:"outdated"`
Name string `json:"name"`
AutostartSchedule *string `json:"autostart_schedule,omitempty"`
TTLMillis *int64 `json:"ttl_ms,omitempty"`
LastUsedAt time.Time `json:"last_used_at"`
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
OwnerID uuid.UUID `json:"owner_id"`
OwnerName string `json:"owner_name"`
TemplateID uuid.UUID `json:"template_id"`
TemplateName string `json:"template_name"`
TemplateDisplayName string `json:"template_display_name"`
TemplateIcon string `json:"template_icon"`
LatestBuild WorkspaceBuild `json:"latest_build"`
Outdated bool `json:"outdated"`
Name string `json:"name"`
AutostartSchedule *string `json:"autostart_schedule,omitempty"`
TTLMillis *int64 `json:"ttl_ms,omitempty"`
LastUsedAt time.Time `json:"last_used_at"`
}
type WorkspacesRequest struct {
+1
View File
@@ -788,6 +788,7 @@ export interface Workspace {
readonly owner_name: string
readonly template_id: string
readonly template_name: string
readonly template_display_name: string
readonly template_icon: string
readonly latest_build: WorkspaceBuild
readonly outdated: boolean
@@ -5,7 +5,10 @@ import { FC } from "react"
import { Link as RouterLink } from "react-router-dom"
import { combineClasses } from "util/combineClasses"
import { createDayString } from "util/createDayString"
import { getDisplayWorkspaceBuildInitiatedBy } from "util/workspace"
import {
getDisplayWorkspaceBuildInitiatedBy,
getDisplayWorkspaceTemplateName,
} from "util/workspace"
import { Workspace } from "../../api/typesGenerated"
const Language = {
@@ -36,6 +39,7 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
const initiatedBy = getDisplayWorkspaceBuildInitiatedBy(
workspace.latest_build,
)
const displayTemplateName = getDisplayWorkspaceTemplateName(workspace)
return (
<div className={styles.stats} aria-label={Language.workspaceDetails}>
@@ -46,7 +50,7 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
to={`/templates/${workspace.template_name}`}
className={combineClasses([styles.statsValue, styles.link])}
>
{workspace.template_name}
{displayTemplateName}
</Link>
</div>
<div className={styles.statItem}>
@@ -7,6 +7,7 @@ import { AvatarData } from "components/AvatarData/AvatarData"
import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"
import { FC } from "react"
import { useNavigate } from "react-router-dom"
import { getDisplayWorkspaceTemplateName } from "util/workspace"
import { WorkspaceItemMachineRef } from "../../xServices/workspaces/workspacesXService"
import { LastUsed } from "../LastUsed/LastUsed"
import {
@@ -32,6 +33,7 @@ export const WorkspacesRow: FC<
const workspacePageLink = `/@${workspace.owner_name}/${workspace.name}`
const hasTemplateIcon =
workspace.template_icon && workspace.template_icon !== ""
const displayTemplateName = getDisplayWorkspaceTemplateName(workspace)
return (
<TableRow
@@ -61,7 +63,7 @@ export const WorkspacesRow: FC<
</TableCellLink>
<TableCellLink to={workspacePageLink}>
<TableCellDataPrimary>{workspace.template_name}</TableCellDataPrimary>
<TableCellDataPrimary>{displayTemplateName}</TableCellDataPrimary>
</TableCellLink>
<TableCellLink to={workspacePageLink}>
<TableCellData>
@@ -1,7 +1,10 @@
import { screen, waitFor } from "@testing-library/react"
import { rest } from "msw"
import * as CreateDayString from "util/createDayString"
import { MockWorkspace } from "../../testHelpers/entities"
import {
MockWorkspace,
MockWorkspacesResponse,
} from "../../testHelpers/entities"
import { history, render } from "../../testHelpers/renderHelpers"
import { server } from "../../testHelpers/server"
import WorkspacesPage from "./WorkspacesPage"
@@ -54,5 +57,9 @@ describe("WorkspacesPage", () => {
{ timeout: 2000 },
)
await screen.findByText(`${MockWorkspace.name}1`)
const templateDisplayNames = await screen.findAllByText(
`${MockWorkspace.template_display_name}`,
)
expect(templateDisplayNames).toHaveLength(MockWorkspacesResponse.count)
})
})
+1
View File
@@ -434,6 +434,7 @@ export const MockWorkspace: TypesGen.Workspace = {
template_id: MockTemplate.id,
template_name: MockTemplate.name,
template_icon: MockTemplate.icon,
template_display_name: MockTemplate.display_name,
outdated: false,
owner_id: MockUser.id,
owner_name: MockUser.username,
+19
View File
@@ -5,6 +5,7 @@ import {
defaultWorkspaceExtension,
getDisplayVersionStatus,
getDisplayWorkspaceBuildInitiatedBy,
getDisplayWorkspaceTemplateName,
isWorkspaceOn,
} from "./workspace"
@@ -120,4 +121,22 @@ describe("util > workspace", () => {
},
)
})
describe("getDisplayWorkspaceTemplateName", () => {
it("display name is not set", async () => {
const workspace: TypesGen.Workspace = {
...Mocks.MockWorkspace,
template_display_name: "",
}
const displayed = getDisplayWorkspaceTemplateName(workspace)
expect(displayed).toEqual(workspace.template_name)
})
it("display name is set", async () => {
const workspace: TypesGen.Workspace = {
...Mocks.MockWorkspace,
}
const displayed = getDisplayWorkspaceTemplateName(workspace)
expect(displayed).toEqual(workspace.template_display_name)
})
})
})
+8
View File
@@ -182,3 +182,11 @@ export const getFaviconByStatus = (
return "favicon"
}
}
export const getDisplayWorkspaceTemplateName = (
workspace: TypesGen.Workspace,
): string => {
return workspace.template_display_name.length > 0
? workspace.template_display_name
: workspace.template_name
}