mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
chore!: fix workspace apps response (#17700)
Fix WorkspaceApp response type to better reflect the schema from https://registry.terraform.io/providers/coder/coder/latest/docs/resources/app.
This commit is contained in:
@ -60,14 +60,14 @@ type WorkspaceApp struct {
|
||||
ID uuid.UUID `json:"id" format:"uuid"`
|
||||
// URL is the address being proxied to inside the workspace.
|
||||
// If external is specified, this will be opened on the client.
|
||||
URL string `json:"url"`
|
||||
URL string `json:"url,omitempty"`
|
||||
// External specifies whether the URL should be opened externally on
|
||||
// the client or not.
|
||||
External bool `json:"external"`
|
||||
// Slug is a unique identifier within the agent.
|
||||
Slug string `json:"slug"`
|
||||
// DisplayName is a friendly name for the app.
|
||||
DisplayName string `json:"display_name"`
|
||||
DisplayName string `json:"display_name,omitempty"`
|
||||
Command string `json:"command,omitempty"`
|
||||
// Icon is a relative path or external URL that specifies
|
||||
// an icon to be displayed in the dashboard.
|
||||
@ -81,7 +81,7 @@ type WorkspaceApp struct {
|
||||
SubdomainName string `json:"subdomain_name,omitempty"`
|
||||
SharingLevel WorkspaceAppSharingLevel `json:"sharing_level" enums:"owner,authenticated,public"`
|
||||
// Healthcheck specifies the configuration for checking app health.
|
||||
Healthcheck Healthcheck `json:"healthcheck"`
|
||||
Healthcheck Healthcheck `json:"healthcheck,omitempty"`
|
||||
Health WorkspaceAppHealth `json:"health"`
|
||||
Hidden bool `json:"hidden"`
|
||||
OpenIn WorkspaceAppOpenIn `json:"open_in"`
|
||||
|
6
site/src/api/typesGenerated.ts
generated
6
site/src/api/typesGenerated.ts
generated
@ -3474,16 +3474,16 @@ export const WorkspaceAgentStatuses: WorkspaceAgentStatus[] = [
|
||||
// From codersdk/workspaceapps.go
|
||||
export interface WorkspaceApp {
|
||||
readonly id: string;
|
||||
readonly url: string;
|
||||
readonly url?: string;
|
||||
readonly external: boolean;
|
||||
readonly slug: string;
|
||||
readonly display_name: string;
|
||||
readonly display_name?: string;
|
||||
readonly command?: string;
|
||||
readonly icon?: string;
|
||||
readonly subdomain: boolean;
|
||||
readonly subdomain_name?: string;
|
||||
readonly sharing_level: WorkspaceAppSharingLevel;
|
||||
readonly healthcheck: Healthcheck;
|
||||
readonly healthcheck?: Healthcheck;
|
||||
readonly health: WorkspaceAppHealth;
|
||||
readonly hidden: boolean;
|
||||
readonly open_in: WorkspaceAppOpenIn;
|
||||
|
@ -150,9 +150,9 @@ describe.each<{
|
||||
|
||||
for (const app of props.agent.apps) {
|
||||
if (app.hidden) {
|
||||
expect(screen.queryByText(app.display_name)).toBeNull();
|
||||
expect(screen.queryByText(app.display_name as string)).toBeNull();
|
||||
} else {
|
||||
expect(screen.getByText(app.display_name)).toBeVisible();
|
||||
expect(screen.getByText(app.display_name as string)).toBeVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -91,8 +91,10 @@ describe("AgentRowPreviewApps", () => {
|
||||
"<AgentRowPreview agent={$testName} /> displays appropriately",
|
||||
({ workspaceAgent }) => {
|
||||
renderComponent(<AgentRowPreview agent={workspaceAgent} />);
|
||||
for (const module of workspaceAgent.apps) {
|
||||
expect(screen.getByText(module.display_name)).toBeInTheDocument();
|
||||
for (const app of workspaceAgent.apps) {
|
||||
expect(
|
||||
screen.getByText(app.display_name as string),
|
||||
).toBeInTheDocument();
|
||||
}
|
||||
|
||||
for (const app of workspaceAgent.display_apps) {
|
||||
|
@ -31,7 +31,7 @@ export const AgentRowPreview: FC<AgentRowPreviewProps> = ({
|
||||
>
|
||||
<Stack direction="row" alignItems="baseline">
|
||||
<div css={styles.agentStatusWrapper}>
|
||||
<div css={styles.agentStatusPreview}></div>
|
||||
<div css={styles.agentStatusPreview} />
|
||||
</div>
|
||||
<Stack
|
||||
alignItems="baseline"
|
||||
|
@ -43,24 +43,15 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
|
||||
const appsHost = proxy.preferredWildcardHostname;
|
||||
const [fetchingSessionToken, setFetchingSessionToken] = useState(false);
|
||||
const [iconError, setIconError] = useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
const username = workspace.owner_name;
|
||||
|
||||
let appSlug = app.slug;
|
||||
let appDisplayName = app.display_name;
|
||||
if (!appSlug) {
|
||||
appSlug = appDisplayName;
|
||||
}
|
||||
if (!appDisplayName) {
|
||||
appDisplayName = appSlug;
|
||||
}
|
||||
const displayName = app.display_name || app.slug;
|
||||
|
||||
const href = createAppLinkHref(
|
||||
window.location.protocol,
|
||||
preferredPathBase,
|
||||
appsHost,
|
||||
appSlug,
|
||||
app.slug,
|
||||
username,
|
||||
workspace,
|
||||
agent,
|
||||
@ -118,7 +109,7 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
|
||||
// This is an external URI like "vscode://", so
|
||||
// it needs to be opened with the browser protocol handler.
|
||||
const shouldOpenAppExternally =
|
||||
app.external && !app.url.startsWith("http");
|
||||
app.external && app.url?.startsWith("http");
|
||||
|
||||
if (shouldOpenAppExternally) {
|
||||
// This is a magic undocumented string that is replaced
|
||||
@ -140,9 +131,7 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
|
||||
// an error message will be displayed.
|
||||
const openAppExternallyFailedTimeout = 500;
|
||||
const openAppExternallyFailed = setTimeout(() => {
|
||||
displayError(
|
||||
`${app.display_name !== "" ? app.display_name : app.slug} must be installed first.`,
|
||||
);
|
||||
displayError(`${displayName} must be installed first.`);
|
||||
}, openAppExternallyFailedTimeout);
|
||||
window.addEventListener("blur", () => {
|
||||
clearTimeout(openAppExternallyFailed);
|
||||
@ -156,7 +145,7 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
|
||||
case "slim-window": {
|
||||
window.open(
|
||||
href,
|
||||
Language.appTitle(appDisplayName, generateRandomString(12)),
|
||||
Language.appTitle(displayName, generateRandomString(12)),
|
||||
"width=900,height=600",
|
||||
);
|
||||
return;
|
||||
@ -169,7 +158,7 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
{appDisplayName}
|
||||
{displayName}
|
||||
{canShare && <ShareIcon app={app} />}
|
||||
</a>
|
||||
</AgentButton>
|
||||
|
@ -124,12 +124,11 @@ export const WorkspaceAppStatus = ({
|
||||
|
||||
let appHref: string | undefined;
|
||||
if (app && agent) {
|
||||
const appSlug = app.slug || app.display_name;
|
||||
appHref = createAppLinkHref(
|
||||
window.location.protocol,
|
||||
preferredPathBase,
|
||||
appsHost,
|
||||
appSlug,
|
||||
app.slug,
|
||||
workspace.owner_name,
|
||||
workspace,
|
||||
agent,
|
||||
|
@ -198,12 +198,11 @@ export const AppStatuses: FC<AppStatusesProps> = ({
|
||||
const agent = agents.find((agent) => agent.id === status.agent_id);
|
||||
|
||||
if (currentApp && agent) {
|
||||
const appSlug = currentApp.slug || currentApp.display_name;
|
||||
appHref = createAppLinkHref(
|
||||
window.location.protocol,
|
||||
preferredPathBase,
|
||||
appsHost,
|
||||
appSlug,
|
||||
currentApp.slug,
|
||||
workspace.owner_name,
|
||||
workspace,
|
||||
agent,
|
||||
|
@ -11,7 +11,7 @@ export const createAppLinkHref = (
|
||||
app: TypesGen.WorkspaceApp,
|
||||
): string => {
|
||||
if (app.external) {
|
||||
return app.url;
|
||||
return app.url as string;
|
||||
}
|
||||
|
||||
// The backend redirects if the trailing slash isn't included, so we add it
|
||||
|
Reference in New Issue
Block a user