1+ import * as React from "react" ;
2+ import { useCallback , useEffect , useRef , useState } from "react" ;
3+ import { ISettingsProvider } from "@paperbits/common/configuration" ;
4+ import { SessionManager } from "@paperbits/common/persistence/sessionManager" ;
5+ import { HttpClient } from "@paperbits/common/http/httpClient" ;
6+ import {
7+ Body1 ,
8+ Body1Strong ,
9+ Breadcrumb ,
10+ BreadcrumbButton ,
11+ BreadcrumbDivider ,
12+ BreadcrumbItem ,
13+ Button ,
14+ DrawerBody ,
15+ DrawerHeader ,
16+ DrawerHeaderTitle ,
17+ OverlayDrawer ,
18+ Spinner ,
19+ Tooltip ,
20+ isTruncatableBreadcrumbContent ,
21+ truncateBreadcrumbLongName
22+ } from "@fluentui/react-components" ;
23+ import { DismissRegular } from "@fluentui/react-icons" ;
24+ import { RouteHelper } from "../../../../../routing/routeHelper" ;
25+ import { Api } from "../../../../../models/api" ;
26+ import { Operation } from "../../../../../models/operation" ;
27+ import { ConsoleOperation } from "../../../../../models/console/consoleOperation" ;
28+ import { ConsoleHeader } from "../../../../../models/console/consoleHeader" ;
29+ import { ConsoleParameter } from "../../../../../models/console/consoleParameter" ;
30+ import { AuthorizationServer } from "../../../../../models/authorizationServer" ;
31+ import { KnownHttpHeaders } from "../../../../../models/knownHttpHeaders" ;
32+ import { ApiService } from "../../../../../services/apiService" ;
33+ import { OAuthService } from "../../../../../services/oauthService" ;
34+ import { ProductService } from "../../../../../services/productService" ;
35+ import { TenantService } from "../../../../../services/tenantService" ;
36+ import { UsersService } from "../../../../../services/usersService" ;
37+ import { ServiceSkuName , TypeOfApi } from "../../../../../constants" ;
38+ import { ConsoleAuthorization } from "./operation-console/ConsoleAuthorization" ;
39+ import { ConsoleBody } from "./operation-console/ConsoleBody" ;
40+ import { ConsoleHeaders } from "./operation-console/ConsoleHeaders" ;
41+ import { ConsoleHosts } from "./operation-console/ConsoleHosts" ;
42+ import { ConsoleParameters } from "./operation-console/ConsoleParameters" ;
43+ import { ConsoleRequestResponse } from "./operation-console/ConsoleRequestResponse" ;
44+ import { getAuthServers , getBackendUrl , loadSubscriptionKeys , setupOAuth } from "./operation-console/consoleUtils" ;
45+
46+ type OperationConsoleProps = {
47+ isOpen : boolean ;
48+ setIsOpen : ( isOpen : boolean ) => void ;
49+ api : Api ;
50+ operation : Operation ;
51+ hostnames : string [ ] ;
52+ useCorsProxy : boolean ;
53+ apiService : ApiService ;
54+ usersService : UsersService ;
55+ productService : ProductService ;
56+ oauthService : OAuthService ;
57+ tenantService : TenantService ;
58+ routeHelper : RouteHelper ;
59+ settingsProvider : ISettingsProvider ;
60+ sessionManager : SessionManager ;
61+ httpClient : HttpClient ;
62+ }
63+
64+ interface StoredCredentials {
65+ grantType : string ;
66+ accessToken : string ;
67+ }
68+
69+ interface OAuthSession {
70+ [ apiName : string ] : StoredCredentials ;
71+ }
72+
73+ export const OperationConsole = ( {
74+ isOpen,
75+ setIsOpen,
76+ api,
77+ operation,
78+ hostnames,
79+ useCorsProxy,
80+ apiService,
81+ usersService,
82+ productService,
83+ oauthService,
84+ tenantService,
85+ routeHelper,
86+ settingsProvider,
87+ sessionManager,
88+ httpClient
89+ } : OperationConsoleProps ) => {
90+ const [ working , setWorking ] = useState < boolean > ( false ) ;
91+ const [ authorizationServers , setAuthorizationServers ] = useState < AuthorizationServer [ ] > ( [ ] ) ;
92+ const [ products , setProducts ] = useState < any [ ] > ( [ ] ) ;
93+ const [ selectedSubscriptionKey , setSelectedSubscriptionKey ] = useState < string > ( null ) ;
94+ const [ isConsumptionMode , setIsConsumptionMode ] = useState < boolean > ( false ) ;
95+ const [ backendUrl , setBackendUrl ] = useState < string > ( "" ) ;
96+
97+ const consoleOperation = useRef ( new ConsoleOperation ( api , operation ) ) ;
98+ const [ forceRerender , setForceRerender ] = useState < number > ( 1 ) ;
99+ const rerender = useCallback ( ( ) => setForceRerender ( old => old + 1 ) , [ ] ) ;
100+
101+ useEffect ( ( ) => {
102+ setWorking ( true ) ;
103+ consoleOperation . current . host . hostname ( hostnames [ 0 ] ) ;
104+ Promise . all ( [
105+ getAuthServers ( api , oauthService ) . then ( authServers => {
106+ setAuthorizationServers ( authServers ) ;
107+ if ( authServers . length > 0 ) {
108+ setupOAuth ( api , authServers [ 0 ] , consoleOperation . current . request . headers ( ) , sessionManager ) . then ( newHeaders => {
109+ consoleOperation . current . request . headers ( newHeaders ) ;
110+ } ) ;
111+ }
112+ } ) ,
113+ loadSubscriptionKeys ( api , apiService , productService , usersService ) . then ( products => {
114+ setProducts ( products ) ;
115+ if ( products . length > 0 ) {
116+ setSelectedSubscriptionKey ( products [ 0 ] . subscriptionKeys [ 0 ] ?. value ) ;
117+ }
118+ } ) ,
119+ getSkuName ( ) . then ( skuName => setIsConsumptionMode ( skuName === ServiceSkuName . Consumption ) ) ,
120+ getBackendUrl ( settingsProvider ) . then ( url => setBackendUrl ( url ) )
121+ ] )
122+ . catch ( error => new Error ( `Unable to load the console details. Error: ${ error . message } ` ) )
123+ . finally ( ( ) => {
124+ setWorking ( false ) ;
125+ } ) ;
126+ } , [ api , operation , consoleOperation ] ) ;
127+
128+ useEffect ( ( ) => {
129+ if ( ! isConsumptionMode && api . type !== TypeOfApi . webSocket ) {
130+ if ( ! consoleOperation . current . request . headers ( ) . some ( header => header . name ( ) === KnownHttpHeaders . CacheControl ) ) {
131+ consoleOperation . current . setHeader ( KnownHttpHeaders . CacheControl , "no-cache" , "string" , "Disable caching." ) ;
132+ }
133+ }
134+ rerender ( ) ;
135+ } , [ api , isConsumptionMode , consoleOperation , rerender ] ) ;
136+
137+ useEffect ( ( ) => {
138+ api . subscriptionRequired && setSubscriptionKeyHeader ( selectedSubscriptionKey || "" ) ;
139+ } , [ selectedSubscriptionKey ] ) ;
140+
141+ const getSkuName = async ( ) : Promise < string > => {
142+ return await tenantService . getServiceSkuName ( ) ;
143+ }
144+
145+ const setSubscriptionHeader = ( key ?: string ) : ConsoleHeader [ ] => {
146+ const headers = consoleOperation . current . request . headers ( ) ;
147+ let subscriptionHeaderName : string = KnownHttpHeaders . OcpApimSubscriptionKey ;
148+
149+ if ( api . subscriptionKeyParameterNames && api . subscriptionKeyParameterNames . header ) {
150+ subscriptionHeaderName = api . subscriptionKeyParameterNames . header ;
151+ }
152+
153+ const newHeaders = headers . filter ( header => header . name ( ) !== subscriptionHeaderName ) ;
154+
155+ const subscriptionHeader = new ConsoleHeader ( ) ;
156+ subscriptionHeader . name ( subscriptionHeaderName ) ;
157+ subscriptionHeader . value ( key || "" ) ;
158+ subscriptionHeader . description = "Subscription key." ;
159+ subscriptionHeader . required = true ;
160+ subscriptionHeader . secret ( true ) ;
161+ subscriptionHeader . type = "string" ;
162+ subscriptionHeader . inputTypeValue ( "password" ) ;
163+
164+ newHeaders . push ( subscriptionHeader ) ;
165+
166+ return newHeaders ;
167+ }
168+
169+ const setSubscriptionKeyHeader = ( key : string = "" ) : void => {
170+ const newHeaders = setSubscriptionHeader ( key ) ;
171+ consoleOperation . current . request . headers ( newHeaders ) ;
172+ rerender ( ) ;
173+ }
174+
175+ const updateHostname = ( hostname : string ) => {
176+ consoleOperation . current . host . hostname ( hostname ) ;
177+ rerender ( ) ;
178+ }
179+
180+ const updateParameters = ( queryParameters : ConsoleParameter [ ] , templateParameters : ConsoleParameter [ ] ) => {
181+ consoleOperation . current . templateParameters ( templateParameters ) ;
182+ consoleOperation . current . request . queryParameters ( queryParameters ) ;
183+ rerender ( ) ;
184+ }
185+
186+ const updateHeaders = ( headers : ConsoleHeader [ ] ) => {
187+ consoleOperation . current . request . headers ( headers ) ;
188+ rerender ( ) ;
189+ }
190+
191+ const updateBody = ( body : string ) => {
192+ consoleOperation . current . request . body ( body ) ;
193+ rerender ( ) ;
194+ }
195+
196+ const updateBodyBinary = ( body : File ) => {
197+ consoleOperation . current . request . binary ( body ) ;
198+ rerender ( ) ;
199+ }
200+
201+ const getApiReferenceUrl = ( ) : string => {
202+ return routeHelper . getApiReferenceUrl ( api . name ) ;
203+ }
204+
205+ const renderBreadcrumbItem = ( name : string , isCurrent : boolean , url ?: string ) => (
206+ < >
207+ { isTruncatableBreadcrumbContent ( name , 20 )
208+ ? < BreadcrumbItem >
209+ < Tooltip withArrow content = { name } relationship = "label" >
210+ < BreadcrumbButton href = { url ?? "" } current = { isCurrent } > { truncateBreadcrumbLongName ( name , 20 ) } </ BreadcrumbButton >
211+ </ Tooltip >
212+ </ BreadcrumbItem >
213+ : < BreadcrumbItem >
214+ < BreadcrumbButton href = { url ?? "" } current = { isCurrent } > { name } </ BreadcrumbButton >
215+ </ BreadcrumbItem >
216+ }
217+ </ >
218+ )
219+
220+ return (
221+ < OverlayDrawer
222+ open = { isOpen }
223+ onOpenChange = { ( _ , { open } ) => setIsOpen ( open ) }
224+ position = "end"
225+ size = "medium"
226+ className = "console-drawer"
227+ >
228+ < DrawerHeader >
229+ < DrawerHeaderTitle
230+ action = {
231+ < Button
232+ aria-label = "Close console"
233+ appearance = "subtle"
234+ icon = { < DismissRegular /> }
235+ onClick = { ( ) => setIsOpen ( false ) }
236+ />
237+ }
238+ >
239+ < Breadcrumb >
240+ { renderBreadcrumbItem ( api . displayName , false , getApiReferenceUrl ( ) ) }
241+ { api . apiVersion &&
242+ < >
243+ < BreadcrumbDivider />
244+ < BreadcrumbItem >
245+ < BreadcrumbButton > { api . apiVersion } </ BreadcrumbButton >
246+ </ BreadcrumbItem >
247+ </ >
248+ }
249+ { api . type !== TypeOfApi . webSocket &&
250+ < >
251+ < BreadcrumbDivider />
252+ { renderBreadcrumbItem ( consoleOperation ?. current . name ?? "" , true ) }
253+ </ >
254+ }
255+ </ Breadcrumb >
256+ </ DrawerHeaderTitle >
257+ </ DrawerHeader >
258+ < DrawerBody >
259+ { ( working || ! consoleOperation . current ) ? < Spinner label = "Loading..." labelPosition = "below" size = "small" />
260+ : < >
261+ { api . type !== TypeOfApi . webSocket && consoleOperation . current . urlTemplate &&
262+ < div className = "console-method" >
263+ < Body1Strong className = { `operation-method method-${ consoleOperation . current . method } ` } >
264+ { consoleOperation . current . method }
265+ </ Body1Strong >
266+ < Body1 className = { `operation-name` } >
267+ { consoleOperation . current . urlTemplate }
268+ </ Body1 >
269+ </ div >
270+ }
271+ { hostnames . length > 1 &&
272+ < ConsoleHosts
273+ hostnames = { hostnames }
274+ updateHostname = { updateHostname }
275+ />
276+ }
277+ { ( authorizationServers . length > 0 || api . subscriptionRequired ) &&
278+ < ConsoleAuthorization
279+ api = { api }
280+ headers = { consoleOperation . current . request . headers ( ) }
281+ products = { products }
282+ subscriptionRequired = { api . subscriptionRequired }
283+ subscriptionKey = { selectedSubscriptionKey }
284+ authorizationServers = { authorizationServers }
285+ sessionManager = { sessionManager }
286+ oauthService = { oauthService }
287+ updateHeaders = { updateHeaders }
288+ selectSubscriptionKey = { setSelectedSubscriptionKey }
289+ />
290+ }
291+ < ConsoleParameters
292+ queryParameters = { consoleOperation . current . request . queryParameters ( ) }
293+ templateParameters = { consoleOperation . current . templateParameters ( ) }
294+ updateParameters = { updateParameters }
295+ />
296+ < ConsoleHeaders
297+ headers = { consoleOperation . current . request . headers ( ) }
298+ updateHeaders = { updateHeaders }
299+ />
300+ { ( consoleOperation . current . canHaveBody || consoleOperation . current . hasBody ( ) ) &&
301+ < ConsoleBody
302+ hasBody = { consoleOperation . current . hasBody ( ) }
303+ request = { consoleOperation . current . request }
304+ updateBody = { updateBody }
305+ updateBodyBinary = { updateBodyBinary }
306+ />
307+ }
308+ < ConsoleRequestResponse
309+ api = { api }
310+ consoleOperation = { consoleOperation . current }
311+ backendUrl = { backendUrl }
312+ useCorsProxy = { useCorsProxy }
313+ httpClient = { httpClient }
314+ forceRerender = { forceRerender }
315+ />
316+ </ >
317+ }
318+ </ DrawerBody >
319+ </ OverlayDrawer >
320+ ) ;
321+ }
0 commit comments