libs/logic-apps-shared/src/designer-client-services/lib/base/connector.ts (220 lines of code) (raw):

import { getIntl } from '../../../intl/src'; import type { OpenAPIV2, OperationInfo } from '../../../utils/src'; import { ArgumentException, equals, ConnectorServiceException, ConnectorServiceErrorCode } from '../../../utils/src'; import type { IConnectorService, ListDynamicValue, ManagedIdentityRequestProperties, TreeDynamicExtension, TreeDynamicValue, } from '../connector'; import { getClientRequestIdFromHeaders } from '../helpers'; import type { IHttpClient } from '../httpClient'; import type { IntlShape } from 'react-intl'; type GetSchemaFunction = (args: Record<string, any>) => Promise<OpenAPIV2.SchemaObject>; type GetValuesFunction = (args: Record<string, any>) => Promise<ListDynamicValue[]>; export interface BaseConnectorServiceOptions { apiVersion: string; baseUrl: string; httpClient: IHttpClient; clientSupportedOperations: OperationInfo[]; schemaClient?: Record<string, GetSchemaFunction>; valuesClient?: Record<string, GetValuesFunction>; apiHubServiceDetails?: { apiVersion: string; baseUrl: string; }; } export abstract class BaseConnectorService implements IConnectorService { constructor(protected readonly options: BaseConnectorServiceOptions) { const { apiVersion, baseUrl, httpClient, clientSupportedOperations, schemaClient, valuesClient } = options; if (!apiVersion) { throw new ArgumentException('apiVersion required'); } if (!baseUrl) { throw new ArgumentException('baseUrl required'); } if (!httpClient) { throw new ArgumentException('httpClient required'); } if (!clientSupportedOperations) { throw new ArgumentException('clientSupportedOperations required'); } if (!schemaClient) { throw new ArgumentException('schemaClient required'); } if (!valuesClient) { throw new ArgumentException('valuesClient required'); } } abstract getLegacyDynamicContent( connectionId: string, connectorId: string, parameters: Record<string, any>, managedIdentityProperties?: ManagedIdentityRequestProperties ): Promise<any>; abstract getListDynamicValues( connectionId: string | undefined, connectorId: string, operationId: string, parameters: Record<string, any>, dynamicState: any, isManagedIdentityConnection?: boolean ): Promise<ListDynamicValue[]>; abstract getDynamicSchema( connectionId: string | undefined, connectorId: string, operationId: string, parameters: Record<string, any>, dynamicState: any, isManagedIdentityConnection?: boolean ): Promise<OpenAPIV2.SchemaObject>; abstract getTreeDynamicValues( _connectionId: string | undefined, _connectorId: string, _operationId: string, _parameters: Record<string, any>, _dynamicState: TreeDynamicExtension, isManagedIdentityConnection?: boolean ): Promise<TreeDynamicValue[]>; protected _isClientSupportedOperation(connectorId: string, operationId: string): boolean { return this.options.clientSupportedOperations.some( (operationInfo) => equals(connectorId, operationInfo.connectorId) && equals(operationId, operationInfo.operationId) ); } protected _getInvokeParameters(parameters: Record<string, any>, dynamicState: any): Record<string, any> { const invokeParameters = { ...parameters }; const additionalParameters = dynamicState.parameters; if (additionalParameters) { for (const parameterName of Object.keys(additionalParameters)) { const { value } = additionalParameters[parameterName]; if (value !== undefined) { invokeParameters[parameterName] = value; } } } return invokeParameters; } protected _getResponseFromDynamicApi(responseJson: any, requestUrl: string): any { const intl = getIntl(); const connectorResponse = responseJson.response ?? responseJson; if (connectorResponse.statusCode === 'OK') { return connectorResponse.body; } const clientRequestId = getClientRequestIdFromHeaders(connectorResponse.headers); const defaultErrorMessage = intl.formatMessage( { defaultMessage: 'Error executing the API - {url}', id: 'XHQwyJ', description: 'Error message to show on dynamic call failure' }, { url: requestUrl } ); const errorMessage = this._getErrorMessageFromConnectorResponse(connectorResponse, defaultErrorMessage, intl, clientRequestId); throw new ConnectorServiceException(ConnectorServiceErrorCode.API_EXECUTION_FAILED_WITH_ERROR, errorMessage, { connectorResponse }); } private _getErrorMessageFromConnectorResponse( response: any, defaultErrorMessage: string, intl: IntlShape, clientRequestId?: string ): string { const { body: { error, message }, statusCode, } = response; let errorMessage: string; if (statusCode !== undefined && message) { const errorCode = statusCode; errorMessage = intl.formatMessage( { defaultMessage: `Error code: ''{errorCode}'', Message: ''{message}''.`, id: '04AwK7', description: 'Dynamic call error message. Do not remove the double single quotes around the placeholder texts, as it is needed to wrap the placeholder text in single quotes.', }, { errorCode, message } ); } else { errorMessage = error?.message ?? defaultErrorMessage; } return clientRequestId ? `${errorMessage} ${intl.formatMessage( { defaultMessage: `More diagnostic information: x-ms-client-request-id is ''{clientRequestId}''.`, id: 'cWpWiU', description: "Diagnostics information for error message. Don't remove the double single quotes around the placeholder text, which is needed to wrap the placeholder text in single quotes.", }, { clientRequestId } )}` : errorMessage; } protected async _executeAzureDynamicApi( dynamicInvokeUrl: string, apiVersion: string, parameters: Record<string, any>, properties?: ManagedIdentityRequestProperties | { workflowReference: { id: string } } ): Promise<any> { const intl = getIntl(); const method = parameters['method']; const baseUri = `${dynamicInvokeUrl}/dynamicInvoke`; const queryParameters = { 'api-version': apiVersion }; const request = { method, path: parameters['path'], body: parameters['body'], queries: parameters['queries'], headers: parameters['headers'], }; const content = properties ? { request, properties } : { request }; try { // Fetch the initial page of data const initialResponse = await this._fetchData(baseUri, queryParameters, content); // Collect all the values from the initial and subsequent pages const allValues = await this._getAllPagedValues(initialResponse, request.headers); return { ...initialResponse, value: allValues }; } catch (ex: any) { throw this._handleError(ex, intl, method, baseUri, parameters['path']); } } private async _fetchData(baseUri: string, queryParameters: Record<string, any>, content: any) { const initialResponse = await this.options.httpClient.post({ uri: baseUri, queryParameters, content, }); return this._getResponseFromDynamicApi(initialResponse, baseUri); } private async _getAllPagedValues(initialResponse: any, headers: Record<string, any>): Promise<any[]> { let pageData = initialResponse; const allValues: any[] = Array.isArray(pageData.value) ? [...pageData.value] : []; let nextLink: string | undefined = pageData.nextLink || pageData['@odata.nextLink']; while (nextLink) { const pagedResponse = await this.options.httpClient.get({ uri: nextLink, headers, }); pageData = this._getResponseFromDynamicApi({ response: { statusCode: 'OK', body: pagedResponse, headers } }, nextLink); if (!pageData || !Array.isArray(pageData.value)) { throw new Error(`Invalid response at nextLink: ${nextLink}`); } allValues.push(...pageData.value); nextLink = pageData.nextLink || pageData['@odata.nextLink']; } return allValues; } private _handleError(ex: any, intl: IntlShape, method: string, baseUri: string, path: string) { const errorMessage = ex.message ?? intl.formatMessage( { defaultMessage: "Error occurred while executing the following API parameters: ''{parameters}''", id: 'VYqwse', description: 'Error message when executing dynamic API in managed connector. Do not remove the double single quotes around the placeholder text.', }, { parameters: path } ); return new ConnectorServiceException( ConnectorServiceErrorCode.API_EXECUTION_FAILED, errorMessage, { requestMethod: method, uri: baseUri, inputPath: path }, ex ); } }