Skip to content

Commit

Permalink
Extended "Operation details" widget with "Use CORS proxy" option. (#1233
Browse files Browse the repository at this point in the history
)
  • Loading branch information
azaslonov authored Apr 6, 2021
1 parent 186d9d8 commit 2d0cdab
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,24 +1,49 @@
<fieldset class="form flex-item flex-item-grow" data-bind="scrollable: {}">
<div class="form-group">
<label for="enableConsole" class="form-label">
<input type="checkbox" id="enableConsole" name="enableConsole" data-bind="checked: enableConsole" />
Enable API console
</label>
</div>
<div class="form-group">
<label for="enableScrollTo" class="form-label">
<input type="checkbox" id="enableScrollTo" name="enableScrollTo" data-bind="checked: enableScrollTo" />
Automatically scroll to operation name
<div class="form-group form-group-collapsible">
<label class="form-label">
<a href="#" class="form-group-toggle" title="Toggle section"
data-bind="collapse: '#operationConsoleCollapse'"></a>
<b>Operation console</b>
</label>
<div id="operationConsoleCollapse" class="collapse form-group-collapse">
<div class="form-group">
<label for="enableConsole" class="form-label">
<input type="checkbox" id="enableConsole" name="enableConsole" data-bind="checked: enableConsole" />
Enable API console
</label>
</div>
<div class="form-group">
<label for="useCorsProxy" class="form-label">
<input type="checkbox" id="useCorsProxy" name="useCorsProxy" data-bind="checked: useCorsProxy" />
Use CORS proxy
</label>
</div>
</div>
</div>

<div class="form-group">
<label for="defaultSchemaView" class="form-label">
Default schema view
<div class="form-group form-group-collapsible">
<label class="form-label">
<a href="#" class="form-group-toggle" title="Toggle section"
data-bind="collapse: '#miscellaneousCollapse'"></a>
<b>Miscellaneous</b>
</label>
<select class="form-control" data-bind="value: defaultSchemaView">
<option value="table">Table</value>
<option value="raw">Raw schema</value>
</select>
<div id="miscellaneousCollapse" class="collapse form-group-collapse">
<div class="form-group">
<label for="enableScrollTo" class="form-label">
<input type="checkbox" id="enableScrollTo" name="enableScrollTo"
data-bind="checked: enableScrollTo" />
Automatically scroll to operation name
</label>
</div>
<div class="form-group">
<label for="defaultSchemaView" class="form-label">
Default schema view
</label>
<select class="form-control" data-bind="value: defaultSchemaView">
<option value="table">Table</value>
<option value="raw">Raw schema</value>
</select>
</div>
</div>
</div>
</fieldset>
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ export class OperationDetailsEditor {
public readonly enableConsole: ko.Observable<boolean>;
public readonly enableScrollTo: ko.Observable<boolean>;
public readonly defaultSchemaView: ko.Observable<string>;
public readonly useCorsProxy: ko.Observable<boolean>;

constructor() {
this.enableConsole = ko.observable();
this.enableScrollTo = ko.observable();
this.defaultSchemaView = ko.observable();
this.useCorsProxy = ko.observable();
}

@Param()
Expand All @@ -27,15 +29,19 @@ export class OperationDetailsEditor {
@OnMounted()
public async initialize(): Promise<void> {
this.enableConsole(this.model.enableConsole);
this.useCorsProxy(this.model.useCorsProxy);
this.enableScrollTo(this.model.enableScrollTo);
this.defaultSchemaView(this.model.defaultSchemaView || "table");

this.enableConsole.subscribe(this.applyChanges);
this.useCorsProxy.subscribe(this.applyChanges);
this.enableScrollTo.subscribe(this.applyChanges);
this.defaultSchemaView.subscribe(this.applyChanges);
}

private applyChanges(): void {
this.model.enableConsole = this.enableConsole();
this.model.useCorsProxy = this.useCorsProxy();
this.model.enableScrollTo = this.enableScrollTo();
this.model.defaultSchemaView = this.defaultSchemaView();
this.onChange(this.model);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export class OperationDetailsViewModelBinder implements ViewModelBinder<Operatio
enableConsole: model.enableConsole,
enableScrollTo: model.enableScrollTo,
authorizationServers: model.authorizationServers,
defaultSchemaView: model.defaultSchemaView
defaultSchemaView: model.defaultSchemaView,
useCorsProxy: model.useCorsProxy
};

viewModel.config(JSON.stringify(runtimeConfig));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as ko from "knockout";
import * as validation from "knockout.validation";
import template from "./operation-console.html";
import { Component, Param, OnMounted } from "@paperbits/common/ko/decorators";
import { ISettingsProvider } from "@paperbits/common/configuration";
import { Operation } from "../../../../../models/operation";
import { ApiService } from "../../../../../services/apiService";
import { ConsoleOperation } from "../../../../../models/console/consoleOperation";
Expand All @@ -15,7 +16,7 @@ import { ProductService } from "../../../../../services/productService";
import { UsersService } from "../../../../../services/usersService";
import { TenantService } from "../../../../../services/tenantService";
import { ServiceSkuName, TypeOfApi } from "../../../../../constants";
import { HttpClient, HttpRequest } from "@paperbits/common/http";
import { HttpClient, HttpRequest, HttpResponse } from "@paperbits/common/http";
import { Revision } from "../../../../../models/revision";
import { templates } from "./templates/templates";
import { ConsoleParameter } from "../../../../../models/console/consoleParameter";
Expand All @@ -26,6 +27,7 @@ import { OAuthService } from "../../../../../services/oauthService";
import { AuthorizationServer } from "../../../../../models/authorizationServer";
import { SessionManager } from "../../../../../authentication/sessionManager";
import { OAuthSession, StoredCredentials } from "./oauthSession";
import { ResponsePackage } from "./responsePackage";

const oauthSessionKey = "oauthSession";

Expand Down Expand Up @@ -56,6 +58,7 @@ export class OperationConsole {
public readonly selectedGrantType: ko.Observable<string>;
public isConsumptionMode: boolean;
public templates: Object;
public backendUrl: string;

constructor(
private readonly apiService: ApiService,
Expand All @@ -65,7 +68,8 @@ export class OperationConsole {
private readonly httpClient: HttpClient,
private readonly routeHelper: RouteHelper,
private readonly oauthService: OAuthService,
private readonly sessionManager: SessionManager
private readonly sessionManager: SessionManager,
private readonly settingsProvider: ISettingsProvider
) {
this.templates = templates;
this.products = ko.observable();
Expand Down Expand Up @@ -94,7 +98,7 @@ export class OperationConsole {
this.isHostnameWildcarded = ko.computed(() => this.selectedHostname().includes("*"));
this.selectedGrantType = ko.observable();
this.authorizationServer = ko.observable();

this.useCorsProxy = ko.observable(false);
this.wildcardSegment = ko.observable();

validation.rules["maxFileSize"] = {
Expand Down Expand Up @@ -126,10 +130,14 @@ export class OperationConsole {
@Param()
public authorizationServer: ko.Observable<AuthorizationServer>;

@Param()
public useCorsProxy: ko.Observable<boolean>;

@OnMounted()
public async initialize(): Promise<void> {
const skuName = await this.tenantService.getServiceSkuName();
this.isConsumptionMode = skuName === ServiceSkuName.Consumption;
this.backendUrl = await this.settingsProvider.getSetting<string>("backendUrl");

await this.resetConsole();

Expand Down Expand Up @@ -419,6 +427,48 @@ export class OperationConsole {
this.sendRequest();
}

public async sendFromBrowser<T>(request: HttpRequest): Promise<HttpResponse<T>> {
const response = await this.httpClient.send<any>(request);
return response;
}

public async sendFromProxy<T>(request: HttpRequest): Promise<HttpResponse<T>> {
if (request.body) {
request.body = Buffer.from(request.body);
}

const formData = new FormData();
const requestPackage = new Blob([JSON.stringify(request)], { type: "application/json" });
formData.append("requestPackage", requestPackage);

const baseProxyUrl = this.backendUrl || "";
const apiName = this.api().name;

const proxiedRequest: HttpRequest = {
url: `${baseProxyUrl}/send`,
method: "POST",
headers: [{ name: "X-Ms-Api-Name", value: apiName }],
body: formData
};

const proxiedResponse = await this.httpClient.send<ResponsePackage>(proxiedRequest);
const responsePackage = proxiedResponse.toObject();

const responseBodyBuffer = responsePackage.body
? Buffer.from(responsePackage.body.data)
: null;

const response: any = {
headers: responsePackage.headers,
statusCode: responsePackage.statusCode,
statusText: responsePackage.statusMessage,
body: responseBodyBuffer,
toText: () => responseBodyBuffer.toString("utf8")
};

return response;
}

private async sendRequest(): Promise<void> {
this.requestError(null);
this.sendingRequest(true);
Expand Down Expand Up @@ -455,7 +505,10 @@ export class OperationConsole {
body: payload
};

const response = await this.httpClient.send(request);
const response = this.useCorsProxy()
? await this.sendFromProxy(request)
: await this.sendFromBrowser(request);

this.responseHeadersString(response.headers.map(x => `${x.name}: ${x.value}`).join("\n"));

const knownStatusCode = KnownStatusCodes.find(x => x.code === response.statusCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ <h3>Definitions</h3>
<!-- ko if: $component.consoleIsOpen -->
<div class="detachable-right scrollable flex-grow animation-fade-in">
<operation-console class="test flex flex-column"
params="{ api: api, operation: operation, hostnames: hostnames, authorizationServer: associatedAuthServer }">
params="{ api: api, operation: operation, hostnames: hostnames, authorizationServer: associatedAuthServer, useCorsProxy: useCorsProxy }">
</operation-console>
</div>
<!-- /ko -->
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class OperationDetails {
this.consoleIsOpen = ko.observable();
this.definitions = ko.observableArray<TypeDefinition>();
this.defaultSchemaView = ko.observable("table");
this.useCorsProxy = ko.observable();
this.requestUrlSample = ko.computed(() => {
if (!this.api() || !this.operation()) {
return null;
Expand All @@ -82,6 +83,9 @@ export class OperationDetails {
@Param()
public enableConsole: boolean;

@Param()
public useCorsProxy: ko.Observable<boolean>;

@Param()
public enableScrollTo: boolean;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface NameValuePair {
name: string;
value: string;
}

export interface ResponsePackage {
statusCode: number;
statusMessage: string;
headers: NameValuePair[];
body: any;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ export interface OperationDetailsContract extends Contract {
* Indicates whether operation details should appear in the visible area (for example if API details is too long).
*/
enableScrollTo?: boolean;

/**
* Indicates whether the Test console should use CORS proxy vs direct calls from the browser.
*/
useCorsProxy?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export class OperationDetailsModel {
*/
public enableScrollTo?: boolean;

/**
* Indicates whether the Test console should use CORS proxy vs direct calls from the browser.
*/
public useCorsProxy?: boolean;

constructor() {
this.enableConsole = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class OperationDetailsModelBinder implements IModelBinder<OperationDetail
model.enableScrollTo = contract.enableScrollTo !== undefined && contract.enableScrollTo === true;
model.defaultSchemaView = contract.defaultSchemaView || "table";
model.authorizationServers = await this.oauthService.getOAuthServers();
model.useCorsProxy = contract.useCorsProxy;

return model;
}
Expand All @@ -30,7 +31,8 @@ export class OperationDetailsModelBinder implements IModelBinder<OperationDetail
type: "operationDetails",
enableConsole: model.enableConsole,
enableScrollTo: model.enableScrollTo,
defaultSchemaView: model.defaultSchemaView
defaultSchemaView: model.defaultSchemaView,
useCorsProxy: model.useCorsProxy
};

return contract;
Expand Down

0 comments on commit 2d0cdab

Please sign in to comment.