src/components/operations/operation-details/react/runtime/OperationDetailsWebsocket.tsx (168 lines of code) (raw):
import * as React from "react";
import { useEffect, useState } from "react";
import { ISettingsProvider } from "@paperbits/common/configuration";
import { SessionManager } from "@paperbits/common/persistence/sessionManager";
import { HttpClient } from "@paperbits/common/http/httpClient";
import { Stack } from "@fluentui/react";
import { Badge, Button, Spinner, Tooltip } from "@fluentui/react-components";
import { Copy16Regular } from "@fluentui/react-icons";
import { Operation } from "../../../../../models/operation";
import { Api } from "../../../../../models/api";
import { Tag } from "../../../../../models/tag";
import { ApiService } from "../../../../../services/apiService";
import { OAuthService } from "../../../../../services/oauthService";
import { UsersService } from "../../../../../services/usersService";
import { ProductService } from "../../../../../services/productService";
import { RouteHelper } from "../../../../../routing/routeHelper";
import { MarkdownProcessor } from "../../../../utils/react/MarkdownProcessor";
import { getRequestUrl, scrollToOperation } from "./utils";
import { OperationDetailsRuntimeProps } from "./OperationDetailsRuntime";
import { OperationConsole } from "./OperationConsole";
export const OperationDetailsWebsocket = ({
apiName,
apiService,
usersService,
productService,
oauthService,
routeHelper,
settingsProvider,
sessionManager,
httpClient,
enableConsole,
useCorsProxy,
includeAllHostnames,
enableScrollTo
}: OperationDetailsRuntimeProps & {
apiName: string,
apiService: ApiService,
usersService: UsersService,
productService: ProductService,
oauthService: OAuthService,
routeHelper: RouteHelper,
settingsProvider: ISettingsProvider,
sessionManager: SessionManager,
httpClient: HttpClient
}) => {
const [working, setWorking] = useState(false);
const [api, setApi] = useState<Api>(null);
const [operation, setOperation] = useState<Operation>(null);
const [tags, setTags] = useState<Tag[]>([]);
const [hostnames, setHostnames] = useState<string[]>([]);
const [requestUrl, setRequestUrl] = useState<string>(null);
const [isCopied, setIsCopied] = useState(false);
const [isConsoleOpen, setIsConsoleOpen] = useState<boolean>(false);
useEffect(() => {
if (!apiName) return;
setWorking(true);
loadApi().then(loadedApi => setApi(loadedApi));
loadGatewayInfo().then(hostnames => {
hostnames.length > 0 && setHostnames(hostnames);
});
loadOperation().then(loadedValues => {
setOperation(loadedValues.operation);
setTags(loadedValues.tags);
}).finally(() => {
setWorking(false);
enableScrollTo && scrollToOperation();
});
}, [apiName]);
useEffect(() => {
setRequestUrl(getRequestUrl(api, operation, hostnames?.[0]));
}, [api, operation, hostnames]);
useEffect(() => {
isCopied && setTimeout(() => setIsCopied(false), 5000);
}, [isCopied]);
const loadApi = async (): Promise<Api> => {
let api: Api;
try {
api = await apiService.getApi(`apis/${apiName}`);
} catch (error) {
throw new Error(`Unable to load the API. Error: ${error.message}`);
}
return api;
}
const loadOperation = async (): Promise<{operation: Operation, tags: Tag[]}> => {
let operation: Operation;
let tags: Tag[];
try {
// As WS APIs don't expose API operations, selecting the first operation if exists
const operations = await apiService.getOperations(`apis/${apiName}`);
operation = operations?.value[0];
operation && (tags = await apiService.getOperationTags(`apis/${apiName}/operations/${operation.name}`));
} catch (error) {
throw new Error(`Unable to load the operation. Error: ${error.message}`);
}
return {operation, tags};
}
const loadGatewayInfo = async (): Promise<string[]> => {
return await apiService.getApiHostnames(apiName);
}
return (
<div className={"operation-details-container"}>
<h4 className={"operation-details-title"} id={"operation"}>Operation</h4>
{working
? <Spinner label="Loading..." labelPosition="below" size="small" />
: !operation
? <span>No operation selected.</span>
: <div className={"operation-details-content"}>
<OperationConsole
isOpen={isConsoleOpen}
setIsOpen={setIsConsoleOpen}
api={api}
operation={operation}
hostnames={hostnames}
useCorsProxy={useCorsProxy}
apiService={apiService}
usersService={usersService}
productService={productService}
oauthService={oauthService}
routeHelper={routeHelper}
settingsProvider={settingsProvider}
sessionManager={sessionManager}
httpClient={httpClient}
/>
<div className={"operation-table"}>
<div className={"operation-table-header"}>
<h5>{operation.displayName}</h5>
{operation.description &&
<div className={"operation-description"}>
<MarkdownProcessor markdownToDisplay={operation.description} />
</div>
}
{tags.length > 0 &&
<Stack horizontal className={"operation-tags"}>
<span className="strong">Tags:</span>
{tags.map(tag => <Badge key={tag.id} color="important" appearance="outline">{tag.name}</Badge>)}
</Stack>
}
</div>
<div className={"operation-table-body"}>
<div className={"operation-table-body-row"}>
<span className={"caption1-strong operation-info-caption ws-caption"}>Socket URL</span>
<span className={"operation-text"}>{requestUrl}</span>
<Tooltip
content={isCopied ? "Copied to clipboard!" : "Copy to clipboard"}
relationship={"description"}
hideDelay={isCopied ? 3000 : 250}
>
<Button
icon={<Copy16Regular />}
appearance="transparent"
onClick={() => {
navigator.clipboard.writeText(requestUrl);
setIsCopied(true);
}}
/>
</Tooltip>
</div>
{api.protocols &&
<div className={"operation-table-body-row"}>
<span className={"caption1-strong operation-info-caption ws-caption"}>Protocol</span>
<span className={"operation-text"}>{api.protocols.join(", ")}</span>
</div>
}
</div>
</div>
{enableConsole && <button className="button" onClick={() => setIsConsoleOpen(true)}>Try this operation</button>}
</div>
}
</div>
);
}