Fix crash in program viewer when trying to display new value type (#3585)

fix https://github.com/Azure/typespec-azure/issues/1016
### Fix crash in the program viewer
When we were trying to display a value(only case right now is using a
default value as property) it would blow up

### Add error boundary to only crash the output view and ability to
retry so the entire UI is not lost

![image](https://github.com/microsoft/typespec/assets/1031227/d856c8c9-41d9-4c86-9c6b-432e60a12d53)
This commit is contained in:
Timothee Guerin 2024-06-14 11:20:30 -07:00 коммит произвёл GitHub
Родитель d319e3324a
Коммит 216f423a8c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
7 изменённых файлов: 68 добавлений и 11 удалений

Просмотреть файл

@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: feature
packages:
- "@typespec/playground"
---
Add error recovery for viewer that crash

Просмотреть файл

@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: fix
packages:
- "@typespec/html-program-viewer"
---
Fix crash in program viewer when trying to display new value type

Просмотреть файл

@ -1,5 +1,6 @@
import { css } from "@emotion/react"; import { css } from "@emotion/react";
import { import {
Entity,
Enum, Enum,
EnumMember, EnumMember,
getNamespaceFullName, getNamespaceFullName,
@ -132,12 +133,12 @@ const NamedTypeUI = <T extends NamedType>({ type, name, properties }: NamedTypeU
return undefined; return undefined;
} }
const render = (x: any) => const render = (x: Entity) =>
action === "ref" ? <TypeReference type={value} /> : <TypeUI type={x} />; action === "ref" ? <TypeReference type={value} /> : <EntityUI entity={x} />;
let valueUI; let valueUI;
if (value === undefined) { if (value === undefined) {
valueUI = value; valueUI = value;
} else if (value.kind) { } else if (value.entityKind) {
valueUI = render(value); valueUI = render(value);
} else if ( } else if (
typeof value === "object" && typeof value === "object" &&
@ -160,10 +161,19 @@ const NamedTypeUI = <T extends NamedType>({ type, name, properties }: NamedTypeU
}; };
interface TypeUIProps { interface TypeUIProps {
type: Type; readonly entity: Entity;
} }
const TypeUI: FunctionComponent<TypeUIProps> = ({ type }) => { const EntityUI: FunctionComponent<TypeUIProps> = ({ entity }) => {
switch (entity.entityKind) {
case "Type":
return <TypeUI type={entity} />;
default:
return null;
}
};
const TypeUI: FunctionComponent<{ type: Type }> = ({ type }) => {
switch (type.kind) { switch (type.kind) {
case "Namespace": case "Namespace":
return <NamespaceUI type={type} />; return <NamespaceUI type={type} />;
@ -379,7 +389,7 @@ const TypeReference: FunctionComponent<{ type: Type }> = ({ type }) => {
if (type.name === "") { if (type.name === "") {
return ( return (
<KeyValueSection> <KeyValueSection>
<TypeUI type={type} /> <EntityUI entity={type} />
</KeyValueSection> </KeyValueSection>
); );
} else { } else {

Просмотреть файл

@ -85,6 +85,7 @@
"monaco-editor": "~0.46.0", "monaco-editor": "~0.46.0",
"react": "~18.3.1", "react": "~18.3.1",
"react-dom": "~18.3.1", "react-dom": "~18.3.1",
"react-error-boundary": "^4.0.13",
"swagger-ui-dist": "^5.17.10", "swagger-ui-dist": "^5.17.10",
"vscode-languageserver": "~9.0.1", "vscode-languageserver": "~9.0.1",
"vscode-languageserver-textdocument": "~1.0.11" "vscode-languageserver-textdocument": "~1.0.11"

Просмотреть файл

@ -26,3 +26,7 @@
.viewer-tabs-container { .viewer-tabs-container {
background-color: var(--colorNeutralBackground3); background-color: var(--colorNeutralBackground3);
} }
.viewer-error {
padding: 20px;
}

Просмотреть файл

@ -1,5 +1,6 @@
import { Tab, TabList, type SelectTabEventHandler } from "@fluentui/react-components"; import { Button, Tab, TabList, type SelectTabEventHandler } from "@fluentui/react-components";
import { useCallback, useMemo, useState, type FunctionComponent } from "react"; import { useCallback, useMemo, useState, type FunctionComponent } from "react";
import { ErrorBoundary, type FallbackProps } from "react-error-boundary";
import type { PlaygroundEditorsOptions } from "../playground.js"; import type { PlaygroundEditorsOptions } from "../playground.js";
import type { CompilationState, CompileResult, FileOutputViewer, ProgramViewer } from "../types.js"; import type { CompilationState, CompileResult, FileOutputViewer, ProgramViewer } from "../types.js";
import { createFileViewer } from "./file-viewer.js"; import { createFileViewer } from "./file-viewer.js";
@ -81,10 +82,12 @@ const OutputViewInternal: FunctionComponent<{
return ( return (
<div className={style["output-view"]}> <div className={style["output-view"]}>
<div className={style["output-content"]}> <div className={style["output-content"]}>
<viewer.render <ErrorBoundary fallbackRender={fallbackRender}>
program={compilationResult.program} <viewer.render
outputFiles={compilationResult.outputFiles} program={compilationResult.program}
/> outputFiles={compilationResult.outputFiles}
/>
</ErrorBoundary>
</div> </div>
<div className={style["viewer-tabs-container"]}> <div className={style["viewer-tabs-container"]}>
<TabList <TabList
@ -106,3 +109,13 @@ const OutputViewInternal: FunctionComponent<{
</div> </div>
); );
}; };
function fallbackRender({ error, resetErrorBoundary }: FallbackProps) {
return (
<div role="alert" className={style["viewer-error"]}>
<h2>Something went wrong:</h2>
<div style={{ color: "red" }}>{error.toString()}</div>
<Button onClick={resetErrorBoundary}>Try again</Button>
</div>
);
}

13
pnpm-lock.yaml сгенерированный
Просмотреть файл

@ -754,6 +754,9 @@ importers:
react-dom: react-dom:
specifier: ~18.3.1 specifier: ~18.3.1
version: 18.3.1(react@18.3.1) version: 18.3.1(react@18.3.1)
react-error-boundary:
specifier: ^4.0.13
version: 4.0.13(react@18.3.1)
swagger-ui-dist: swagger-ui-dist:
specifier: ^5.17.10 specifier: ^5.17.10
version: 5.17.10 version: 5.17.10
@ -14203,6 +14206,7 @@ packages:
/ieee754@1.2.1: /ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
requiresBuild: true
/ignore-walk@6.0.4: /ignore-walk@6.0.4:
resolution: {integrity: sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw==} resolution: {integrity: sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw==}
@ -18161,6 +18165,15 @@ packages:
react-is: 18.1.0 react-is: 18.1.0
dev: true dev: true
/react-error-boundary@4.0.13(react@18.3.1):
resolution: {integrity: sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==}
peerDependencies:
react: '>=16.13.1'
dependencies:
'@babel/runtime': 7.24.1
react: 18.3.1
dev: false
/react-error-overlay@6.0.11: /react-error-overlay@6.0.11:
resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==} resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==}
dev: false dev: false