From db176063bcdfe310e9875587ab7ee140eb6a7a87 Mon Sep 17 00:00:00 2001 From: Duc Tai Ly Date: Fri, 19 Apr 2024 10:45:14 +0200 Subject: [PATCH 01/14] Custom extension value render --- library/src/components/Extensions.tsx | 7 +++++++ library/src/components/Schema.tsx | 17 +++++++++++++++++ library/src/config/config.ts | 3 +++ library/src/config/default.ts | 1 + 4 files changed, 28 insertions(+) diff --git a/library/src/components/Extensions.tsx b/library/src/components/Extensions.tsx index e7ce0663..5186f099 100644 --- a/library/src/components/Extensions.tsx +++ b/library/src/components/Extensions.tsx @@ -3,12 +3,19 @@ import React from 'react'; import { Schema } from './Schema'; import { SchemaHelpers } from '../helpers'; +import { AsyncAPIDocumentInterface } from '@asyncapi/parser'; interface Props { name?: string; item: any; } +export interface ExtensionComponentProps { + propertyName: string; + propertyValue: V; + document: AsyncAPIDocumentInterface; +} + export const Extensions: React.FunctionComponent = ({ name = 'Extensions', item, diff --git a/library/src/components/Schema.tsx b/library/src/components/Schema.tsx index 2de7cbe8..4d8fc300 100644 --- a/library/src/components/Schema.tsx +++ b/library/src/components/Schema.tsx @@ -3,6 +3,7 @@ import { SchemaInterface } from '@asyncapi/parser'; import { Href, CollapseButton, Markdown, Extensions } from './index'; import { SchemaHelpers } from '../helpers'; +import { useConfig, useSpec } from '../contexts'; interface Props { schemaName?: React.ReactNode; @@ -37,6 +38,8 @@ export const Schema: React.FunctionComponent = ({ const { reverse, deepExpanded } = useContext(SchemaContext); const [expanded, setExpanded] = useState(propExpanded || isArray); const [deepExpand, setDeepExpand] = useState(false); + const config = useConfig(); + const document = useSpec(); useEffect(() => { if (!isArray) { @@ -87,6 +90,20 @@ export const Schema: React.FunctionComponent = ({ schemaName ); + const extensions = config.extensions; + + if (typeof schemaName === 'string' && extensions && extensions[schemaName]) { + const Component = extensions[schemaName]; + + return ( + + ); + } + return ( >; } export interface ShowConfig { diff --git a/library/src/config/default.ts b/library/src/config/default.ts index 725fff4c..cdfe881a 100644 --- a/library/src/config/default.ts +++ b/library/src/config/default.ts @@ -32,4 +32,5 @@ export const defaultConfig: ConfigInterface = { receiveLabel: RECEIVE_TEXT_LABEL_DEFAULT_TEXT, requestLabel: REQUEST_LABEL_DEFAULT_TEXT, replyLabel: REPLIER_LABEL_DEFAULT_TEXT, + extensions: {}, }; From 22f07b389b89d5696801d8a61d852c38c9617419 Mon Sep 17 00:00:00 2001 From: Duc Tai Ly Date: Thu, 25 Apr 2024 16:33:41 +0200 Subject: [PATCH 02/14] Add parent to ExtensionComponentProps and move changes to Extensions component --- library/src/components/Extensions.tsx | 84 ++++++++++++++++++++++++--- library/src/components/Schema.tsx | 17 ------ 2 files changed, 77 insertions(+), 24 deletions(-) diff --git a/library/src/components/Extensions.tsx b/library/src/components/Extensions.tsx index 5186f099..70ccaeff 100644 --- a/library/src/components/Extensions.tsx +++ b/library/src/components/Extensions.tsx @@ -1,9 +1,11 @@ -import React from 'react'; +import React, { useContext, useState } from 'react'; import { Schema } from './Schema'; import { SchemaHelpers } from '../helpers'; -import { AsyncAPIDocumentInterface } from '@asyncapi/parser'; +import { AsyncAPIDocumentInterface, BaseModel } from '@asyncapi/parser'; +import { useConfig, useSpec } from '../contexts'; +import { CollapseButton } from './CollapseButton'; interface Props { name?: string; @@ -14,23 +16,91 @@ export interface ExtensionComponentProps { propertyName: string; propertyValue: V; document: AsyncAPIDocumentInterface; + parent: BaseModel; } +const SchemaContext = React.createContext({ + reverse: false, +}); + export const Extensions: React.FunctionComponent = ({ name = 'Extensions', item, }) => { + const { reverse } = useContext(SchemaContext); + const [expanded, setExpanded] = useState(false); + const [deepExpand, setDeepExpand] = useState(false); + + const config = useConfig(); + const document = useSpec(); + const extensions = SchemaHelpers.getCustomExtensions(item); if (!extensions || !Object.keys(extensions).length) { return null; } - const schema = SchemaHelpers.jsonToSchema(extensions); + if (!config.extensions || !Object.keys(config.extensions).length) { + const schema = SchemaHelpers.jsonToSchema(extensions); + return ( + schema && ( +
+ +
+ ) + ); + } + return ( - schema && ( -
- +
+
+
+ <> + setExpanded(prev => !prev)} + expanded={expanded} + > + {name} + + + +
+
+
+ {Object.keys(extensions) + .sort() + .map(extensionKey => { + if (config.extensions && config.extensions[extensionKey]) { + const CustomExtensionComponent = config.extensions[extensionKey]; + return ( + + ); + } else { + const extensionSchema = SchemaHelpers.jsonToSchema( + extensions[extensionKey], + ); + return ( +
+ +
+ ); + } + })}
- ) +
); }; diff --git a/library/src/components/Schema.tsx b/library/src/components/Schema.tsx index 4d8fc300..2de7cbe8 100644 --- a/library/src/components/Schema.tsx +++ b/library/src/components/Schema.tsx @@ -3,7 +3,6 @@ import { SchemaInterface } from '@asyncapi/parser'; import { Href, CollapseButton, Markdown, Extensions } from './index'; import { SchemaHelpers } from '../helpers'; -import { useConfig, useSpec } from '../contexts'; interface Props { schemaName?: React.ReactNode; @@ -38,8 +37,6 @@ export const Schema: React.FunctionComponent = ({ const { reverse, deepExpanded } = useContext(SchemaContext); const [expanded, setExpanded] = useState(propExpanded || isArray); const [deepExpand, setDeepExpand] = useState(false); - const config = useConfig(); - const document = useSpec(); useEffect(() => { if (!isArray) { @@ -90,20 +87,6 @@ export const Schema: React.FunctionComponent = ({ schemaName ); - const extensions = config.extensions; - - if (typeof schemaName === 'string' && extensions && extensions[schemaName]) { - const Component = extensions[schemaName]; - - return ( - - ); - } - return ( Date: Thu, 25 Apr 2024 16:45:16 +0200 Subject: [PATCH 03/14] Address Sonar issue --- library/src/components/Extensions.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/src/components/Extensions.tsx b/library/src/components/Extensions.tsx index 49161906..628f1d09 100644 --- a/library/src/components/Extensions.tsx +++ b/library/src/components/Extensions.tsx @@ -81,7 +81,9 @@ export const Extensions: React.FunctionComponent = ({ } ${expanded ? 'block' : 'hidden'}`} > {Object.keys(extensions) - .sort() + .sort((extension1, extension2) => + extension1.localeCompare(extension2), + ) .map(extensionKey => { if (config.extensions && config.extensions[extensionKey]) { const CustomExtensionComponent = config.extensions[extensionKey]; From 13a8a6c056e5e679391f07eca94123d6b0d39a5e Mon Sep 17 00:00:00 2001 From: Duc Tai Ly <124802873+ductaily@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:15:52 +0200 Subject: [PATCH 04/14] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Maciej UrbaƄczyk --- library/src/components/Extensions.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/library/src/components/Extensions.tsx b/library/src/components/Extensions.tsx index 628f1d09..64639072 100644 --- a/library/src/components/Extensions.tsx +++ b/library/src/components/Extensions.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import React, { useContext, useState } from 'react'; +import React, { useState } from 'react'; import { Schema } from './Schema'; @@ -23,15 +23,11 @@ export interface ExtensionComponentProps { parent: BaseModel; } -const SchemaContext = React.createContext({ - reverse: false, -}); export const Extensions: React.FunctionComponent = ({ name = 'Extensions', item, }) => { - const { reverse } = useContext(SchemaContext); const [expanded, setExpanded] = useState(false); const [deepExpand, setDeepExpand] = useState(false); From 23401a9264fca8b0e6a550d049c896320c2b6d6a Mon Sep 17 00:00:00 2001 From: Duc Tai Ly Date: Tue, 30 Apr 2024 09:42:37 +0200 Subject: [PATCH 05/14] Clean up Extension class --- library/src/components/Extensions.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/library/src/components/Extensions.tsx b/library/src/components/Extensions.tsx index 64639072..564f9b29 100644 --- a/library/src/components/Extensions.tsx +++ b/library/src/components/Extensions.tsx @@ -23,7 +23,6 @@ export interface ExtensionComponentProps { parent: BaseModel; } - export const Extensions: React.FunctionComponent = ({ name = 'Extensions', item, @@ -56,14 +55,14 @@ export const Extensions: React.FunctionComponent = ({
<> setExpanded(prev => !prev)} + onClick={() => setExpanded((prev) => !prev)} expanded={expanded} > {name}
{Object.keys(extensions) .sort((extension1, extension2) => extension1.localeCompare(extension2), ) - .map(extensionKey => { + .map((extensionKey) => { if (config.extensions && config.extensions[extensionKey]) { const CustomExtensionComponent = config.extensions[extensionKey]; return ( From 973c1da1da2b6c44dabbf8a61099fe4c33e65b9b Mon Sep 17 00:00:00 2001 From: Duc Tai Ly Date: Mon, 13 May 2024 13:07:03 +0200 Subject: [PATCH 06/14] Address eslint errors --- library/src/components/Extensions.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/src/components/Extensions.tsx b/library/src/components/Extensions.tsx index 564f9b29..0ca9db69 100644 --- a/library/src/components/Extensions.tsx +++ b/library/src/components/Extensions.tsx @@ -16,6 +16,7 @@ interface Props { item: any; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export interface ExtensionComponentProps { propertyName: string; propertyValue: V; @@ -78,10 +79,11 @@ export const Extensions: React.FunctionComponent = ({ extension1.localeCompare(extension2), ) .map((extensionKey) => { - if (config.extensions && config.extensions[extensionKey]) { + if (config.extensions?.[extensionKey]) { const CustomExtensionComponent = config.extensions[extensionKey]; return ( = ({ extensions[extensionKey], ); return ( -
+
); From 4b03c14bbf630c4eb704b0dcd94c93238148db41 Mon Sep 17 00:00:00 2001 From: Duc Tai Ly Date: Tue, 21 May 2024 17:49:29 +0200 Subject: [PATCH 07/14] Fix handling of concatenatedConfig for extensions --- library/src/containers/AsyncApi/Standalone.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/src/containers/AsyncApi/Standalone.tsx b/library/src/containers/AsyncApi/Standalone.tsx index 658aa9b4..e441afd6 100644 --- a/library/src/containers/AsyncApi/Standalone.tsx +++ b/library/src/containers/AsyncApi/Standalone.tsx @@ -69,6 +69,10 @@ class AsyncApiComponent extends Component { ...defaultConfig.sidebar, ...(!!config && config.sidebar), }, + extensions: { + ...defaultConfig.extensions, + ...(!!config && config.extensions), + }, }; if (!asyncapi) { From 3b548f59cf2f303377b6404eae7ae0699191904e Mon Sep 17 00:00:00 2001 From: Duc Tai Ly Date: Tue, 21 May 2024 18:10:40 +0200 Subject: [PATCH 08/14] Add config modification docs --- docs/configuration/config-modification.md | 38 ++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/configuration/config-modification.md b/docs/configuration/config-modification.md index 63326030..cc1ff373 100644 --- a/docs/configuration/config-modification.md +++ b/docs/configuration/config-modification.md @@ -34,6 +34,7 @@ interface ConfigInterface { receiveLabel?: string; requestLabel?: string; replyLabel?: string; + extensions?: Record>; } ``` @@ -93,6 +94,11 @@ interface ConfigInterface { This field contains configuration responsible for customizing the label for response operation. This takes effect when rendering AsyncAPI v3 documents. This field is set to `REPLY` by default. +- **extensions?: Record>** + + This field contains configuration responsible for adding custom extension components. + This field will contain default components. + ## Examples See exemplary component configuration in TypeScript and JavaScript. @@ -103,6 +109,7 @@ See exemplary component configuration in TypeScript and JavaScript. import * as React from "react"; import { render } from "react-dom"; import AsyncAPIComponent, { ConfigInterface } from "@asyncapi/react-component"; +import CustomExtension from "./CustomExtension"; import { schema } from "./mock"; @@ -119,6 +126,9 @@ const config: ConfigInterface = { expand: { messageExamples: false, }, + extensions: { + 'x-custom-extension': CustomExtension + } }; const App = () => ; @@ -126,6 +136,18 @@ const App = () => ; render(, document.getElementById("root")); ``` +```tsx +// CustomExtension.tsx +import { ExtensionComponentProps } from '@asyncapi/react-component/lib/types/components/Extensions'; + +export default function CustomExtension(props: ExtensionComponentProps) { + return
+

{props.propertyName}

+

{props.propertyValue}

+
+} +``` + ### JavaScript ```jsx @@ -155,6 +177,16 @@ const App = () => ; render(, document.getElementById("root")); ``` +```jsx +// CustomExtension.jsx +export default function CustomExtension(props) { + return
+

{props.propertyName}

+

{props.propertyValue}

+
+} +``` + In the above examples, after concatenation with the default configuration, the resulting configuration looks as follows: ```js @@ -181,6 +213,10 @@ In the above examples, after concatenation with the default configuration, the r sendLabel: 'SEND', receiveLabel: 'RECEIVE', requestLabel: 'REQUEST', - replyLabel: 'REPLY' + replyLabel: 'REPLY', + extensions: { + // default extensions... + 'x-custom-extension': CustomExtension + } } ``` From cae58611eab4f48e8d82b157bc07084efc68f3de Mon Sep 17 00:00:00 2001 From: Duc Tai Ly Date: Wed, 22 May 2024 15:45:18 +0200 Subject: [PATCH 09/14] Add test --- library/src/__tests__/index.test.tsx | 51 ++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/library/src/__tests__/index.test.tsx b/library/src/__tests__/index.test.tsx index 3bb01e8b..5832731e 100644 --- a/library/src/__tests__/index.test.tsx +++ b/library/src/__tests__/index.test.tsx @@ -13,6 +13,7 @@ import krakenMultipleChannels from './docs/v3/kraken-websocket-request-reply-mul import streetlightsKafka from './docs/v3/streetlights-kafka.json'; import streetlightsMqtt from './docs/v3/streetlights-mqtt.json'; import websocketGemini from './docs/v3/websocket-gemini.json'; +import { ExtensionComponentProps } from '../components'; jest.mock('use-resize-observer', () => ({ __esModule: true, @@ -216,4 +217,54 @@ describe('AsyncAPI component', () => { expect(result.container.querySelector('#introduction')).toBeDefined(), ); }); + + test('should work with custom extensions', async () => { + const schema = { + asyncapi: '2.0.0', + info: { + title: 'Example AsyncAPI', + version: '0.1.0', + }, + channels: { + 'smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured': + { + subscribe: { + message: { + $ref: '#/components/messages/lightMeasured', + }, + }, + }, + }, + components: { + messages: { + lightMeasured: { + name: 'lightMeasured', + title: 'Light measured', + contentType: 'application/json', + 'x-custom-extension': 'Custom extension value', + }, + }, + }, + }; + + const CustomExtension = (props: ExtensionComponentProps) => ( +
{props.propertyValue}
+ ); + + const result = render( + , + ); + + await waitFor(async () => { + expect(result.container.querySelector('#introduction')).toBeDefined(); + expect(result.container.querySelector('#custom-extension')).toBeDefined(); + }); + }); }); From 8caea6e119c28fb86c4d31aa475ce69c2b278bcf Mon Sep 17 00:00:00 2001 From: Duc Tai Ly Date: Wed, 22 May 2024 16:04:29 +0200 Subject: [PATCH 10/14] Add example for x-x extension --- .../supportedExtensions/XExtension.tsx | 28 +++++++++++++++++++ library/src/config/default.ts | 5 +++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 library/src/components/supportedExtensions/XExtension.tsx diff --git a/library/src/components/supportedExtensions/XExtension.tsx b/library/src/components/supportedExtensions/XExtension.tsx new file mode 100644 index 00000000..20bb3d6b --- /dev/null +++ b/library/src/components/supportedExtensions/XExtension.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { ExtensionComponentProps } from '../Extensions'; + +export default function XExtension({ + propertyValue, +}: ExtensionComponentProps) { + const onClickHandler = () => { + window.open(propertyValue, '_blank'); + }; + + return ( +
+ + + +
+ ); +} diff --git a/library/src/config/default.ts b/library/src/config/default.ts index cdfe881a..9de4be43 100644 --- a/library/src/config/default.ts +++ b/library/src/config/default.ts @@ -7,6 +7,7 @@ import { SEND_LABEL_DEFAULT_TEXT, SUBSCRIBE_LABEL_DEFAULT_TEXT, } from '../constants'; +import XExtension from '../components/supportedExtensions/XExtension'; export const defaultConfig: ConfigInterface = { schemaID: '', @@ -32,5 +33,7 @@ export const defaultConfig: ConfigInterface = { receiveLabel: RECEIVE_TEXT_LABEL_DEFAULT_TEXT, requestLabel: REQUEST_LABEL_DEFAULT_TEXT, replyLabel: REPLIER_LABEL_DEFAULT_TEXT, - extensions: {}, + extensions: { + 'x-x': XExtension, + }, }; From f9863980640547a9b476d91a21f1e6797a03306d Mon Sep 17 00:00:00 2001 From: Duc Tai Ly Date: Wed, 22 May 2024 16:25:04 +0200 Subject: [PATCH 11/14] Add viewBox to x logo --- .../src/components/supportedExtensions/XExtension.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/library/src/components/supportedExtensions/XExtension.tsx b/library/src/components/supportedExtensions/XExtension.tsx index 20bb3d6b..bc74e945 100644 --- a/library/src/components/supportedExtensions/XExtension.tsx +++ b/library/src/components/supportedExtensions/XExtension.tsx @@ -1,20 +1,27 @@ import React from 'react'; import { ExtensionComponentProps } from '../Extensions'; +/** + * See . + */ export default function XExtension({ propertyValue, }: ExtensionComponentProps) { const onClickHandler = () => { - window.open(propertyValue, '_blank'); + window.open(`https://x.com/${propertyValue}`, '_blank'); }; return ( -
+
From a34f50a8391a5ab24088f6e2e3acbb4f78f6a974 Mon Sep 17 00:00:00 2001 From: Duc Tai Ly Date: Wed, 19 Jun 2024 10:18:11 +0200 Subject: [PATCH 12/14] Update package-lock.json --- package-lock.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/package-lock.json b/package-lock.json index e4aa3654..3ee01a30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27354,6 +27354,26 @@ "webpack-cli": "5.1.4" } }, + "web-component/node_modules/@asyncapi/react-component": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@asyncapi/react-component/-/react-component-1.4.10.tgz", + "integrity": "sha512-ejANS06yj1ZM4YDtsRi0g7h3EEJLGusewjzeugK+tGntNAKVZRvTPUXhbSDMhTARHuZXhUGLlITIno7N1aXapw==", + "dependencies": { + "@asyncapi/avro-schema-parser": "^3.0.22", + "@asyncapi/openapi-schema-parser": "^3.0.22", + "@asyncapi/parser": "^3.0.14", + "@asyncapi/protobuf-schema-parser": "^3.2.12", + "highlight.js": "^10.7.2", + "isomorphic-dompurify": "^0.13.0", + "marked": "^4.0.14", + "openapi-sampler": "^1.2.1", + "use-resize-observer": "^8.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "web-component/node_modules/use-resize-observer": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-8.0.0.tgz", From 32cf262cc6420b47d8fbd8a81ebb490a738b9f88 Mon Sep 17 00:00:00 2001 From: Duc Tai Ly Date: Wed, 19 Jun 2024 10:22:38 +0200 Subject: [PATCH 13/14] Fix lint error --- library/src/__tests__/index.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/__tests__/index.test.tsx b/library/src/__tests__/index.test.tsx index 5832731e..ab4ea610 100644 --- a/library/src/__tests__/index.test.tsx +++ b/library/src/__tests__/index.test.tsx @@ -262,7 +262,7 @@ describe('AsyncAPI component', () => { />, ); - await waitFor(async () => { + await waitFor(() => { expect(result.container.querySelector('#introduction')).toBeDefined(); expect(result.container.querySelector('#custom-extension')).toBeDefined(); }); From 3e57a3fcee889ac7423ca6567b36830f56ca9f31 Mon Sep 17 00:00:00 2001 From: Duc Tai Ly Date: Wed, 31 Jul 2024 14:58:51 +0200 Subject: [PATCH 14/14] Add x-x extensions example. --- playground/specs/streetlights.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/playground/specs/streetlights.ts b/playground/specs/streetlights.ts index 519aa26d..44563ec3 100644 --- a/playground/specs/streetlights.ts +++ b/playground/specs/streetlights.ts @@ -136,6 +136,7 @@ servers: channels: smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured: + x-x: AsyncAPISpec x-security: $ref: '#/components/securitySchemes/supportedOauthFlows/flows/clientCredentials' description: The topic on which measured values may be produced and consumed.