Skip to content

FUI - Operation console #2655

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 68 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
3b01379
APIs List V2 - init
JMach1 Feb 6, 2024
ef3f4cc
APIs List V2 - APIs List v2
JMach1 Feb 13, 2024
14f67a1
APIs List V2 - @fluentui/react-components
JMach1 Feb 20, 2024
715ed6d
APIs List V2 - TODO
JMach1 Feb 20, 2024
4a4bae4
APIs List V2 - api-list base working
JMach1 Feb 21, 2024
6e1aa55
APIs List V2 - api-list pagination working
JMach1 Feb 21, 2024
0059561
APIs List V2 - api-list first row info
JMach1 Feb 21, 2024
8a6e07a
APIs List V2 - api-list cards & fuiTheme fix
JMach1 Feb 22, 2024
2aa751d
APIs List V2 - ApisCards
JMach1 Mar 4, 2024
ebdddde
APIs List V2 - cleanup
JMach1 Mar 4, 2024
46126b6
Merge branch 'fui/main' into fui/api-list
JMach1 Mar 4, 2024
1392b6b
APIs List V2 - npm i
JMach1 Mar 4, 2024
fd4d151
APIs List V2 - search & group by WiP
JMach1 Mar 19, 2024
8894f4d
FUI - List of operations
jsorohova Apr 26, 2024
b368be4
FUI - List of operations - GQL
jsorohova May 7, 2024
38677f2
FUI - API details
jsorohova May 10, 2024
234dab4
FUI - API details
jsorohova May 13, 2024
4d91f06
FUI - Operation list - CR fixes and router listener
jsorohova May 15, 2024
cb06e04
Merge branch 'js/fui/operation-list' of https://github.com/Azure/api-…
jsorohova May 15, 2024
092d9b3
FUI - API details - router listener
jsorohova May 15, 2024
95e769b
FUI - Operation details - Websocket
jsorohova May 16, 2024
90f0f12
APIs list - group by tag & sorting WiP (TODO)
JMach1 May 20, 2024
a98f4a9
FUI - List of operations (#2475)
jsorohova May 20, 2024
a6eae64
Merge conflict fixes
jsorohova May 20, 2024
5e877df
Merge branch 'js/fui/api-details' of https://github.com/Azure/api-man…
jsorohova May 21, 2024
4b5d273
Products list - table & cards views
JMach1 May 21, 2024
161f918
Merge remote-tracking branch 'refs/remotes/origin/fui/master' into fu…
JMach1 May 21, 2024
c0ff185
merge - styling refactor
JMach1 May 21, 2024
6ca6e5b
page size fix
JMach1 May 21, 2024
2c9d719
Merge branch 'refs/heads/fui/api-list' into fui/products-list
JMach1 May 21, 2024
8c6d9b4
merge fix
JMach1 May 21, 2024
769b983
refactor of style handling
JMach1 May 21, 2024
a407642
markdown processing
JMach1 May 21, 2024
bf7e358
margin adjusted
JMach1 May 21, 2024
0d61bed
Merge branch 'refs/heads/fui/api-list' into fui/products-list
JMach1 May 21, 2024
c8be66d
merge adaptation
JMach1 May 21, 2024
e2bf237
chevron icon fix
JMach1 May 21, 2024
03ec2a0
allowViewSwitching for table views
JMach1 May 21, 2024
38bc0b5
FUI - group by tag implemented
JMach1 May 22, 2024
244b5e5
Merge conflict fixes
jsorohova May 22, 2024
44e3887
Merge branch 'fui/products-list' of https://github.com/Azure/api-mana…
jsorohova May 22, 2024
95c9693
FUI - Table styles
jsorohova May 23, 2024
6112f04
FUI - Operation details - REST
jsorohova May 23, 2024
94ecf8c
FUI - Operation details - REST
jsorohova Jun 4, 2024
f7460a3
Merge conflicts fix
jsorohova Jun 4, 2024
410bb75
Merge branch 'fui/master' of https://github.com/Azure/api-management-…
jsorohova Jun 5, 2024
c6c48f9
FUI - Operations details - representation
jsorohova Jun 11, 2024
7732968
Merge conflict fix
jsorohova Jun 11, 2024
80b70d2
FUI - Operation details - Definitions
jsorohova Jun 11, 2024
ad55244
FUI - APIs list style adjustments
jsorohova Jun 12, 2024
e5f25d4
FUI - Operation details - Enum definition and scroll to operation
jsorohova Jun 12, 2024
021fca3
Merge conflict fix
jsorohova Jun 13, 2024
70c3b67
Merge conflict fix
jsorohova Jun 18, 2024
e607cc0
FUI - Operation details - GraphQL
jsorohova Jun 20, 2024
3ebce00
Merge conflict fix
jsorohova Jun 20, 2024
a66de1a
FUI - Operation details - CR fixes
jsorohova Jun 21, 2024
cf52ad2
Merge branch 'fui/master' of https://github.com/Azure/api-management-…
jsorohova Jun 26, 2024
130c704
FUI - Operation details - GQL
jsorohova Jul 1, 2024
d57919e
Merge branch 'fui/master' of https://github.com/Azure/api-management-…
jsorohova Jul 1, 2024
d771e7d
FUI - Operation details - GQL Union Type
jsorohova Jul 2, 2024
913b09c
Merge conflict fix
jsorohova Jul 2, 2024
a7d59ca
FUI - Operation console
jsorohova Jul 24, 2024
508532a
Merge branch 'fui/master' of https://github.com/Azure/api-management-…
jsorohova Jul 24, 2024
37e99e8
FUI - Operation console - Request/response refactoring
jsorohova Jul 25, 2024
4a75dc7
FUI - Operation console - Adjusted console params
jsorohova Jul 25, 2024
eae93d9
WIP - FUI - GQL console
jsorohova Jul 31, 2024
79b75ec
FUI - GQL console
jsorohova Aug 15, 2024
8160b33
Code review fix
jsorohova Aug 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
import * as React from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { ISettingsProvider } from "@paperbits/common/configuration";
import { SessionManager } from "@paperbits/common/persistence/sessionManager";
import { HttpClient } from "@paperbits/common/http/httpClient";
import {
Body1,
Body1Strong,
Breadcrumb,
BreadcrumbButton,
BreadcrumbDivider,
BreadcrumbItem,
Button,
DrawerBody,
DrawerHeader,
DrawerHeaderTitle,
OverlayDrawer,
Spinner,
Tooltip,
isTruncatableBreadcrumbContent,
truncateBreadcrumbLongName
} from "@fluentui/react-components";
import { DismissRegular } from "@fluentui/react-icons";
import { RouteHelper } from "../../../../../routing/routeHelper";
import { Api } from "../../../../../models/api";
import { Operation } from "../../../../../models/operation";
import { ConsoleOperation } from "../../../../../models/console/consoleOperation";
import { ConsoleHeader } from "../../../../../models/console/consoleHeader";
import { ConsoleParameter } from "../../../../../models/console/consoleParameter";
import { AuthorizationServer } from "../../../../../models/authorizationServer";
import { KnownHttpHeaders } from "../../../../../models/knownHttpHeaders";
import { ApiService } from "../../../../../services/apiService";
import { OAuthService } from "../../../../../services/oauthService";
import { ProductService } from "../../../../../services/productService";
import { TenantService } from "../../../../../services/tenantService";
import { UsersService } from "../../../../../services/usersService";
import { ServiceSkuName, TypeOfApi } from "../../../../../constants";
import { ConsoleAuthorization } from "./operation-console/ConsoleAuthorization";
import { ConsoleBody } from "./operation-console/ConsoleBody";
import { ConsoleHeaders } from "./operation-console/ConsoleHeaders";
import { ConsoleHosts } from "./operation-console/ConsoleHosts";
import { ConsoleParameters } from "./operation-console/ConsoleParameters";
import { ConsoleRequestResponse } from "./operation-console/ConsoleRequestResponse";
import { getAuthServers, getBackendUrl, loadSubscriptionKeys, setupOAuth } from "./operation-console/consoleUtils";

type OperationConsoleProps = {
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
api: Api;
operation: Operation;
hostnames: string[];
useCorsProxy: boolean;
apiService: ApiService;
usersService: UsersService;
productService: ProductService;
oauthService: OAuthService;
tenantService: TenantService;
routeHelper: RouteHelper;
settingsProvider: ISettingsProvider;
sessionManager: SessionManager;
httpClient: HttpClient;
}

interface StoredCredentials {
grantType: string;
accessToken: string;
}

interface OAuthSession {
[apiName: string]: StoredCredentials;
}

export const OperationConsole = ({
isOpen,
setIsOpen,
api,
operation,
hostnames,
useCorsProxy,
apiService,
usersService,
productService,
oauthService,
tenantService,
routeHelper,
settingsProvider,
sessionManager,
httpClient
}: OperationConsoleProps) => {
const [working, setWorking] = useState<boolean>(false);
const [authorizationServers, setAuthorizationServers] = useState<AuthorizationServer[]>([]);
const [products, setProducts] = useState<any[]>([]);
const [selectedSubscriptionKey, setSelectedSubscriptionKey] = useState<string>(null);
const [isConsumptionMode, setIsConsumptionMode] = useState<boolean>(false);
const [backendUrl, setBackendUrl] = useState<string>("");

const consoleOperation = useRef(new ConsoleOperation(api, operation));
const [forceRerender, setForceRerender] = useState<number>(1);
const rerender = useCallback(() => setForceRerender(old => old + 1), []);

useEffect(() => {
setWorking(true);
consoleOperation.current.host.hostname(hostnames[0]);
Promise.all([
getAuthServers(api, oauthService).then(authServers => {
setAuthorizationServers(authServers);
if (authServers.length > 0) {
setupOAuth(api, authServers[0], consoleOperation.current.request.headers(), sessionManager).then(newHeaders => {
consoleOperation.current.request.headers(newHeaders);
});
}
}),
loadSubscriptionKeys(api, apiService, productService, usersService).then(products => {
setProducts(products);
if (products.length > 0) {
setSelectedSubscriptionKey(products[0].subscriptionKeys[0]?.value);
}
}),
getSkuName().then(skuName => setIsConsumptionMode(skuName === ServiceSkuName.Consumption)),
getBackendUrl(settingsProvider).then(url => setBackendUrl(url))
])
.catch(error => new Error(`Unable to load the console details. Error: ${error.message}`))
.finally(() => {
setWorking(false);
});
}, [api, operation, consoleOperation]);

useEffect(() => {
if (!isConsumptionMode && api.type !== TypeOfApi.webSocket) {
if (!consoleOperation.current.request.headers().some(header => header.name() === KnownHttpHeaders.CacheControl)) {
consoleOperation.current.setHeader(KnownHttpHeaders.CacheControl, "no-cache", "string", "Disable caching.");
}
}
rerender();
}, [api, isConsumptionMode, consoleOperation, rerender]);

useEffect(() => {
api.subscriptionRequired && setSubscriptionKeyHeader(selectedSubscriptionKey || "");
}, [selectedSubscriptionKey]);

const getSkuName = async (): Promise<string> => {
return await tenantService.getServiceSkuName();
}

const setSubscriptionHeader = (key?: string): ConsoleHeader[] => {
const headers = consoleOperation.current.request.headers();
let subscriptionHeaderName: string = KnownHttpHeaders.OcpApimSubscriptionKey;

if (api.subscriptionKeyParameterNames && api.subscriptionKeyParameterNames.header) {
subscriptionHeaderName = api.subscriptionKeyParameterNames.header;
}

const newHeaders = headers.filter(header => header.name() !== subscriptionHeaderName);

const subscriptionHeader = new ConsoleHeader();
subscriptionHeader.name(subscriptionHeaderName);
subscriptionHeader.value(key || "");
subscriptionHeader.description = "Subscription key.";
subscriptionHeader.required = true;
subscriptionHeader.secret(true);
subscriptionHeader.type = "string";
subscriptionHeader.inputTypeValue("password");

newHeaders.push(subscriptionHeader);

return newHeaders;
}

const setSubscriptionKeyHeader = (key: string = ""): void => {
const newHeaders = setSubscriptionHeader(key);
consoleOperation.current.request.headers(newHeaders);
rerender();
}

const updateHostname = (hostname: string) => {
consoleOperation.current.host.hostname(hostname);
rerender();
}

const updateParameters = (queryParameters: ConsoleParameter[], templateParameters: ConsoleParameter[]) => {
consoleOperation.current.templateParameters(templateParameters);
consoleOperation.current.request.queryParameters(queryParameters);
rerender();
}

const updateHeaders = (headers: ConsoleHeader[]) => {
consoleOperation.current.request.headers(headers);
rerender();
}

const updateBody = (body: string) => {
consoleOperation.current.request.body(body);
rerender();
}

const updateBodyBinary = (body: File) => {
consoleOperation.current.request.binary(body);
rerender();
}

const getApiReferenceUrl = (): string => {
return routeHelper.getApiReferenceUrl(api.name);
}

const renderBreadcrumbItem = (name: string, isCurrent: boolean, url?: string) => (
<>
{isTruncatableBreadcrumbContent(name, 20)
? <BreadcrumbItem>
<Tooltip withArrow content={name} relationship="label">
<BreadcrumbButton href={url ?? ""} current={isCurrent}>{truncateBreadcrumbLongName(name, 20)}</BreadcrumbButton>
</Tooltip>
</BreadcrumbItem>
: <BreadcrumbItem>
<BreadcrumbButton href={url ?? ""} current={isCurrent}>{name}</BreadcrumbButton>
</BreadcrumbItem>
}
</>
)

return (
<OverlayDrawer
open={isOpen}
onOpenChange={(_, { open }) => setIsOpen(open)}
position="end"
size="medium"
className="console-drawer"
>
<DrawerHeader>
<DrawerHeaderTitle
action={
<Button
aria-label="Close console"
appearance="subtle"
icon={<DismissRegular />}
onClick={() => setIsOpen(false)}
/>
}
>
<Breadcrumb>
{renderBreadcrumbItem(api.displayName, false, getApiReferenceUrl())}
{api.apiVersion &&
<>
<BreadcrumbDivider />
<BreadcrumbItem>
<BreadcrumbButton>{api.apiVersion}</BreadcrumbButton>
</BreadcrumbItem>
</>
}
{api.type !== TypeOfApi.webSocket &&
<>
<BreadcrumbDivider />
{renderBreadcrumbItem(consoleOperation?.current.name ?? "", true)}
</>
}
</Breadcrumb>
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
{(working || !consoleOperation.current) ? <Spinner label="Loading..." labelPosition="below" size="small" />
: <>
{api.type !== TypeOfApi.webSocket && consoleOperation.current.urlTemplate &&
<div className="console-method">
<Body1Strong className={`operation-method method-${consoleOperation.current.method}`}>
{consoleOperation.current.method}
</Body1Strong>
<Body1 className={`operation-name`}>
{consoleOperation.current.urlTemplate}
</Body1>
</div>
}
{hostnames.length > 1 &&
<ConsoleHosts
hostnames={hostnames}
updateHostname={updateHostname}
/>
}
{(authorizationServers.length > 0 || api.subscriptionRequired) &&
<ConsoleAuthorization
api={api}
headers={consoleOperation.current.request.headers()}
products={products}
subscriptionRequired={api.subscriptionRequired}
subscriptionKey={selectedSubscriptionKey}
authorizationServers={authorizationServers}
sessionManager={sessionManager}
oauthService={oauthService}
updateHeaders={updateHeaders}
selectSubscriptionKey={setSelectedSubscriptionKey}
/>
}
<ConsoleParameters
queryParameters={consoleOperation.current.request.queryParameters()}
templateParameters={consoleOperation.current.templateParameters()}
updateParameters={updateParameters}
/>
<ConsoleHeaders
headers={consoleOperation.current.request.headers()}
updateHeaders={updateHeaders}
/>
{(consoleOperation.current.canHaveBody || consoleOperation.current.hasBody()) &&
<ConsoleBody
hasBody={consoleOperation.current.hasBody()}
request={consoleOperation.current.request}
updateBody={updateBody}
updateBodyBinary={updateBodyBinary}
/>
}
<ConsoleRequestResponse
api={api}
consoleOperation={consoleOperation.current}
backendUrl={backendUrl}
useCorsProxy={useCorsProxy}
httpClient={httpClient}
forceRerender={forceRerender}
/>
</>
}
</DrawerBody>
</OverlayDrawer>
);
}
Loading