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