Skip to content

Commit

Permalink
Path browsers in the document viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardo-forina committed Nov 21, 2024
1 parent 261706b commit ef91fa3
Show file tree
Hide file tree
Showing 13 changed files with 324 additions and 44 deletions.
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@patternfly/react-icons": "^6.0.0",
"@patternfly/react-table": "^6.0.0",
"@statelyai/inspect": "^0.4.0",
"@total-typescript/ts-reset": "^0.6.1",
"@types/lodash": "^4",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
Expand Down
8 changes: 4 additions & 4 deletions packages/ui/src/OpenApiEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import { fromPromise } from "xstate";
import { EditorSidebar } from "./components/EditorSidebar";
import { OpenApiEditorMachine } from "./OpenApiEditorMachine.ts";
import {
Document,
DocumentDataType,
DocumentNavigation,
DocumentPath,
DocumentResponse,
DocumentRoot,
EditorModel,
NodeDataType,
NodePath,
Expand Down Expand Up @@ -68,7 +68,7 @@ export type OpenApiEditorProps = {
spec: string;
parseOpenApi: (document: string) => Promise<void>;
getEditorState: (filter: string) => Promise<EditorModel>;
getDocumentRootSnapshot: () => Promise<DocumentRoot>;
getDocumentSnapshot: () => Promise<Document>;
getPathSnapshot: (path: NodePath) => Promise<DocumentPath>;
getDataTypeSnapshot: (path: NodeDataType) => Promise<DocumentDataType>;
getResponseSnapshot: (path: NodeResponse) => Promise<DocumentResponse>;
Expand Down Expand Up @@ -104,7 +104,7 @@ export const OpenApiEditor = forwardRef<OpenApiEditorRef, OpenApiEditorProps>(
spec,
parseOpenApi,
getEditorState,
getDocumentRootSnapshot,
getDocumentSnapshot,
getPathSnapshot,
getDataTypeSnapshot,
getResponseSnapshot,
Expand Down Expand Up @@ -137,7 +137,7 @@ export const OpenApiEditor = forwardRef<OpenApiEditorRef, OpenApiEditorProps>(

const documentRootDesigner = DocumentDesignerMachine.provide({
actors: {
getDocumentRootSnapshot: fromPromise(() => getDocumentRootSnapshot()),
getDocumentSnapshot: fromPromise(() => getDocumentSnapshot()),
updateDocumentTitle: fromPromise(({ input }) =>
updateDocumentTitle(input)
),
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/OpenApiEditorMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ export const OpenApiEditorMachine = setup({
return spawn("pathDesigner", {
input: {
parentRef: self,
path: context.selectedNode,
node: context.selectedNode,
editable: context.view === "design",
},
});
Expand Down
15 changes: 13 additions & 2 deletions packages/ui/src/OpenApiEditorModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,23 @@ export type Operation = {
};

export type DocumentPath = {
node: NodePath;
summary: string;
description: string;
servers: Server[];
queryParameters: "TODO";
headerParameters: "TODO";
cookieParameters: "TODO";
operations: Operation[];
operations: {
get?: Operation;
put?: Operation;
post?: Operation;
delete?: Operation;
options?: Operation;
head?: Operation;
patch?: Operation;
trace?: Operation;
};
};

export type DataTypeProperty = {
Expand Down Expand Up @@ -83,7 +93,7 @@ export type SecurityRequirement = {
schemes: string[];
};

export type DocumentRoot = {
export type Document = {
title: string;
version: string;
description: string;
Expand All @@ -96,6 +106,7 @@ export type DocumentRoot = {
servers: Server[];
securityScheme: SecurityScheme[];
securityRequirements: SecurityRequirement[];
paths: DocumentPath[];
};

export type NodeRoot = {
Expand Down
74 changes: 64 additions & 10 deletions packages/ui/src/OpenApiEditorWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import YAML from "yaml";

import {
DataTypeProperty,
Document,
DocumentDataType,
DocumentNavigation,
DocumentPath,
DocumentResponse,
DocumentRoot,
EditorModel,
NavigationDataType,
NavigationPath,
Expand Down Expand Up @@ -205,16 +205,48 @@ export async function parseOpenApi(schema: string) {
}
}

export async function getPathSnapshot(node: NodePath): Promise<DocumentPath> {
const path = resolveNode(node.nodePath);
function oasOperationToOperation(
operation?: DM.Operation
): Operation | undefined {
if (operation) {
return {
summary: operation.summary,
description: operation.description,
id: operation.operationId,
tags: [],
servers: [],
queryParameters: "TODO",
headerParameters: "TODO",
cookieParameters: "TODO",
requestBody: undefined,
responses: [],
securityRequirements: [],
};
}
}

function oasNodeToPath(_path: DM.Node): DocumentPath {
if (document.is3xDocument()) {
const pathOas30 = path as DM.Oas30PathItem;
const summary = pathOas30.summary;
const description = pathOas30.description;
const path = _path as DM.Oas30PathItem;
const summary = path.summary;
const description = path.description;
const servers: Server[] = [];
const operations: Operation[] = [];
const operations = {
get: oasOperationToOperation(path.get),
put: oasOperationToOperation(path.put),
post: oasOperationToOperation(path.post),
delete: oasOperationToOperation(path.delete),
options: oasOperationToOperation(path.options),
head: oasOperationToOperation(path.head),
patch: oasOperationToOperation(path.patch),
trace: oasOperationToOperation(path["trace"]),
};
return {
node: {
type: "path",
path: path.getPath(),
nodePath: DM.Library.createNodePath(_path).toString(),
},
summary,
description,
servers,
Expand All @@ -224,18 +256,39 @@ export async function getPathSnapshot(node: NodePath): Promise<DocumentPath> {
cookieParameters: "TODO",
};
} else {
const path = _path as DM.Oas20PathItem;
const operations = {
get: oasOperationToOperation(path.get),
put: oasOperationToOperation(path.put),
post: oasOperationToOperation(path.post),
delete: oasOperationToOperation(path.delete),
options: oasOperationToOperation(path.options),
head: oasOperationToOperation(path.head),
patch: oasOperationToOperation(path.patch),
trace: undefined,
};
return {
node: {
type: "path",
path: path.getPath(),
nodePath: DM.Library.createNodePath(_path).toString(),
},
summary: "",
description: "",
servers: [],
operations: [],
operations,
queryParameters: "TODO",
headerParameters: "TODO",
cookieParameters: "TODO",
};
}
}

export async function getPathSnapshot(node: NodePath): Promise<DocumentPath> {
const path = resolveNode(node.nodePath);
return oasNodeToPath(path);
}

export async function getDataTypeSnapshot(
node: NodeDataType
): Promise<DocumentDataType> {
Expand Down Expand Up @@ -315,8 +368,8 @@ export async function getResponseSnapshot(
}
}

export async function getDocumentRootSnapshot(): Promise<DocumentRoot> {
console.log("getDocumentRootSnapshot");
export async function getDocumentSnapshot(): Promise<Document> {
console.log("getDocumentSnapshot");
return {
title: document.info.title,
version: document.info.version,
Expand Down Expand Up @@ -345,6 +398,7 @@ export async function getDocumentRootSnapshot(): Promise<DocumentRoot> {
document.security?.map((s) => ({
schemes: s.getSecurityRequirementNames() ?? [],
})) ?? [],
paths: getOasPaths().map(oasNodeToPath),
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/Toc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function Toc({ children }: PropsWithChildren) {
return (
<JumpLinks
className={classes.toc}
scrollableSelector={".apicurio-editor .pf-v6-c-drawer__panel-main"}
scrollableSelector={".apicurio-editor .pf-v6-c-drawer__content"}
isVertical={true}
expandable={{ default: "expandable", lg: "nonExpandable" }}
label={"Table of contents"}
Expand Down
5 changes: 5 additions & 0 deletions packages/ui/src/documentDesigner/Designer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,29 @@ import { SecurityScheme } from "./SecurityScheme.tsx";
import { SecurityRequirements } from "./SecurityRequirements.tsx";
import { useMachineSelector } from "./DocumentDesignerMachineContext.ts";
import { DesignerLayout } from "./DesignerLayout.tsx";
import { Paths } from "./Paths.tsx";

export function Designer() {
const {
tagDefinitionsCount,
serversCount,
securitySchemeCount,
securityRequirementsCount,
pathsCount,
} = useMachineSelector(({ context }) => {
return {
tagDefinitionsCount: context.tags?.length,
serversCount: context.servers?.length,
securitySchemeCount: context.securityScheme?.length,
securityRequirementsCount: context.securityRequirements?.length,
pathsCount: context.paths?.length,
};
});
return (
<DesignerLayout
info={<Info />}
paths={<Paths />}
pathsCount={pathsCount}
contact={<Contact />}
license={<License />}
tagDefinitions={<TagDefinitions />}
Expand Down
38 changes: 24 additions & 14 deletions packages/ui/src/documentDesigner/DesignerLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,44 @@ import { Toc } from "../components/Toc.tsx";
import { TocContainer } from "../components/TocContainer.tsx";
import { Section } from "../components/Section.tsx";
import { ReactNode } from "react";
import { SectionSkeleton } from "../components/SectionSkeleton.tsx";

export function DesignerLayout({
info,
contact,
license,
info = <SectionSkeleton />,
paths = <SectionSkeleton />,
contact = <SectionSkeleton />,
license = <SectionSkeleton />,
tagDefinitions = <SectionSkeleton />,
servers = <SectionSkeleton />,
securityScheme = <SectionSkeleton />,
securityRequirements = <SectionSkeleton />,
pathsCount,
tagDefinitionsCount,
tagDefinitions,
serversCount,
servers,
securitySchemeCount,
securityScheme,
securityRequirementsCount,
securityRequirements,
}: {
info: ReactNode;
contact: ReactNode;
license: ReactNode;
info?: ReactNode;
paths?: ReactNode;
contact?: ReactNode;
license?: ReactNode;
tagDefinitions?: ReactNode;
servers?: ReactNode;
securityScheme?: ReactNode;
securityRequirements?: ReactNode;
pathsCount?: number;
tagDefinitionsCount?: number;
tagDefinitions: ReactNode;
serversCount?: number;
servers: ReactNode;
securitySchemeCount?: number;
securityScheme: ReactNode;
securityRequirementsCount?: number;
securityRequirements: ReactNode;
}) {
return (
<Flex>
<Toc>
<JumpLinksItem href="#info">Info</JumpLinksItem>
<JumpLinksItem href="#contact">Contact</JumpLinksItem>
<JumpLinksItem href="#license">License</JumpLinksItem>
<JumpLinksItem href="#paths">Paths</JumpLinksItem>
<JumpLinksItem href="#tag-definitions">Tag definitions</JumpLinksItem>
<JumpLinksItem href="#servers">Servers</JumpLinksItem>
<JumpLinksItem href="#security-scheme">Security scheme</JumpLinksItem>
Expand All @@ -55,6 +61,10 @@ export function DesignerLayout({
{license}
</Section>
<Divider inset={{ default: "insetNone" }} />
<Section title={"Paths"} id={"paths"} count={pathsCount}>
{paths}
</Section>
<Divider inset={{ default: "insetNone" }} />
<Section
title={"Tag definitions"}
count={tagDefinitionsCount}
Expand Down
14 changes: 7 additions & 7 deletions packages/ui/src/documentDesigner/DocumentDesignerMachine.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ActorRef, assign, fromPromise, sendTo, setup, Snapshot } from "xstate";
import { DocumentRoot } from "../OpenApiEditorModels";
import { Document } from "../OpenApiEditorModels";

type Context = DocumentRoot & {
type Context = Document & {
parentRef: ParentActor;
editable: boolean;
};
Expand Down Expand Up @@ -50,8 +50,8 @@ export const DocumentDesignerMachine = setup({
},
},
actors: {
getDocumentRootSnapshot: fromPromise<DocumentRoot, void>(() =>
Promise.resolve({} as DocumentRoot)
getDocumentSnapshot: fromPromise<Document, void>(() =>
Promise.resolve({} as Document)
),
updateDocumentTitle: fromPromise<void, string>(() => Promise.resolve()),
updateDocumentVersion: fromPromise<void, string>(() => Promise.resolve()),
Expand All @@ -70,7 +70,7 @@ export const DocumentDesignerMachine = setup({
},
actions: {},
}).createMachine({
id: "documentRootDesigner",
id: "documentDesigner",
context: ({ input }) => {
return {
...input,
Expand All @@ -80,7 +80,7 @@ export const DocumentDesignerMachine = setup({
states: {
loading: {
invoke: {
src: "getDocumentRootSnapshot",
src: "getDocumentSnapshot",
onDone: {
target: "idle",
actions: assign(({ event }) => event.output),
Expand All @@ -90,7 +90,7 @@ export const DocumentDesignerMachine = setup({
},
idle: {
invoke: {
src: "getDocumentRootSnapshot",
src: "getDocumentSnapshot",
onDone: {
actions: assign(({ event }) => event.output),
},
Expand Down
Loading

0 comments on commit ef91fa3

Please sign in to comment.