Skip to content

Commit

Permalink
#3992: Run AA Bot Options improvements (#4139)
Browse files Browse the repository at this point in the history
  • Loading branch information
twschiller authored Aug 27, 2022
1 parent 0d5e9bb commit 18d7378
Show file tree
Hide file tree
Showing 10 changed files with 419 additions and 110 deletions.
2 changes: 1 addition & 1 deletion contrib/services/automation-anywhere.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ inputSchema:
password for users that are assigned to the API key generation role.
folderId:
type: string
description: The folder ID containing the bots to run
description: (Deprecated) The folder ID containing the bots to run
format: '\d+'
required:
- username
Expand Down
44 changes: 36 additions & 8 deletions src/background/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,17 @@ export async function deleteCachedAuthData(serviceAuthId: UUID): Promise<void> {
}
}

// In-progress token requests to coalesce multiple requests for the same token. NOTE: this is not a cache, it only
// contains in-progress requests.
const tokenRequests = new Map<UUID, Promise<AuthData>>();

/**
* Exchange credentials for a token, and cache the token response
* @param service
* @param auth
* @see getToken
*/
export async function getToken(
export async function _getToken(
service: IService,
auth: RawServiceConfiguration
): Promise<AuthData> {
if (!service.isToken) {
throw new Error(`Service ${service.id} does not use token authentication`);
}

const { url, data: tokenData } = service.getTokenContext(auth.config);

const {
Expand All @@ -129,6 +127,36 @@ export async function getToken(
return responseData;
}

/**
* Exchange credentials for a token, and cache the token response.
*
* If a request for the token is already in progress, return the existing promise.
*
* @param service
* @param auth
*/
export async function getToken(
service: IService,
auth: RawServiceConfiguration
): Promise<AuthData> {
expectContext("background");

if (!service.isToken) {
throw new Error(`Service ${service.id} does not use token authentication`);
}

const existing = tokenRequests.get(auth.id);
if (existing != null) {
return existing;
}

const tokenPromise = _getToken(service, auth);
tokenRequests.set(auth.id, tokenPromise);
// eslint-disable-next-line promise/prefer-await-to-then -- returning the promise outside this method
tokenPromise.finally(() => tokenRequests.delete(auth.id));
return tokenPromise;
}

function parseResponseParams(url: URL): UnknownObject {
const hasSearchParams = [...url.searchParams.keys()].length > 0;

Expand Down
1 change: 1 addition & 0 deletions src/background/messenger/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const reactivateEveryTab = getNotifier("REACTIVATE_EVERY_TAB", bg);

export const closeTab = getMethod("CLOSE_TAB", bg);
export const deleteCachedAuthData = getMethod("DELETE_CACHED_AUTH", bg);
export const getCachedAuthData = getMethod("GET_CACHED_AUTH", bg);
export const clearServiceCache = getMethod("CLEAR_SERVICE_CACHE", bg);
export const readGoogleBigQuery = getMethod("GOOGLE_BIGQUERY_READ", bg);

Expand Down
4 changes: 3 additions & 1 deletion src/background/messenger/registration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
import * as registry from "@/registry/localRegistry";
import { ensureContentScript } from "@/background/util";
import serviceRegistry from "@/services/registry";
import { deleteCachedAuthData } from "@/background/auth";
import { deleteCachedAuthData, getCachedAuthData } from "@/background/auth";
import { proxyService } from "@/background/requests";
import { readQuery } from "@/contrib/google/bigquery/handlers";
import { getRecord, setRecord } from "@/background/dataStore";
Expand Down Expand Up @@ -123,6 +123,7 @@ declare global {
REQUEST_RUN_IN_ALL: typeof requestRunInBroadcast;

DELETE_CACHED_AUTH: typeof deleteCachedAuthData;
GET_CACHED_AUTH: typeof getCachedAuthData;
PROXY: typeof proxyService;
CLEAR_SERVICE_CACHE: VoidFunction;
GOOGLE_BIGQUERY_READ: typeof readQuery;
Expand Down Expand Up @@ -198,6 +199,7 @@ export default function registerMessenger(): void {
REQUEST_RUN_IN_ALL: requestRunInBroadcast,

DELETE_CACHED_AUTH: deleteCachedAuthData,
GET_CACHED_AUTH: getCachedAuthData,
CLEAR_SERVICE_CACHE: serviceRegistry.clear.bind(serviceRegistry),
PROXY: proxyService,
GOOGLE_BIGQUERY_READ: readQuery,
Expand Down
4 changes: 3 additions & 1 deletion src/background/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ async function authenticate(
}

if (isEmpty(data)) {
throw new Error("No auth data found for token authentication");
throw new Error(
`No auth data found for token authentication response for ${service.id}`
);
}

return service.authenticateRequest(localConfig.config, request, data);
Expand Down
137 changes: 134 additions & 3 deletions src/contrib/automationanywhere/BotOptions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ import {
activeDevToolContextFactory,
menuItemFormStateFactory,
} from "@/testUtils/factories";
import { OutputKey } from "@/core";
import { IService, OutputKey } from "@/core";
import { FormState } from "@/pageEditor/extensionPoints/formStateTypes";
import { render } from "@testing-library/react";
import { PageEditorTabContext } from "@/pageEditor/context";
import { Formik } from "formik";
import { CONTROL_ROOM_SERVICE_ID } from "@/services/constants";
import { AUTOMATION_ANYWHERE_RUN_BOT_ID } from "@/contrib/automationanywhere/RunBot";
import BotOptions from "@/contrib/automationanywhere/BotOptions";
import useDependency from "@/services/useDependency";
import { makeVariableExpression } from "@/testUtils/expressionTestHelpers";
import { uuidv4 } from "@/types/helpers";
import registerDefaultWidgets from "@/components/fields/schemaFields/widgets/registerDefaultWidgets";

jest.mock("webext-detect-page", () => ({
isDevToolsPage: () => true,
Expand Down Expand Up @@ -65,8 +69,8 @@ function makeBaseState() {
id: AUTOMATION_ANYWHERE_RUN_BOT_ID,
config: {
service: null,
releaseKey: null,
inputArguments: {},
fileId: null,
data: {},
},
},
];
Expand All @@ -83,9 +87,136 @@ function renderOptions(formState: FormState = makeBaseState()) {
);
}

beforeAll(() => {
registerDefaultWidgets();
});

describe("BotOptions", () => {
it("should require selected integration", async () => {
const result = renderOptions();

expect(result.queryByText("Workspace")).toBeNull();
expect(result.container).toMatchSnapshot();
});

it("should render default enterprise fields for private workspace", async () => {
(useDependency as jest.Mock).mockReturnValue({
config: {
id: uuidv4(),
config: {
controlRoomUrl: "https://control.room.com",
},
},
service: {} as IService,
hasPermissions: true,
requestPermissions: jest.fn(),
});

const base = makeBaseState();
base.extension.blockPipeline[0].config.service = makeVariableExpression(
"@automationAnywhere"
);

const result = renderOptions(base);

expect(result.queryByText("Workspace")).not.toBeNull();
expect(result.queryByText("Bot")).not.toBeNull();
expect(result.queryByText("Run as Users")).toBeNull();
expect(result.queryByText("Device Pools")).toBeNull();
expect(result.queryByText("Await Result")).not.toBeNull();
expect(result.queryByText("Result Timeout (Milliseconds)")).toBeNull();

// There's non-determinism in React Selects ids: react-select-X-live-region
// expect(result.container).toMatchSnapshot();
});

it("should render community fields", async () => {
(useDependency as jest.Mock).mockReturnValue({
config: {
id: uuidv4(),
config: {
controlRoomUrl:
"https://community2.cloud-2.automationanywhere.digital",
},
},
service: {} as IService,
hasPermissions: true,
requestPermissions: jest.fn(),
});

const base = makeBaseState();
base.extension.blockPipeline[0].config.service = makeVariableExpression(
"@automationAnywhere"
);

const result = renderOptions(base);

// Community only supports private workspace, so we don't show the field
expect(result.queryByText("Workspace")).toBeNull();
expect(result.queryByText("Bot")).not.toBeNull();
expect(result.queryByText("Run as Users")).toBeNull();
expect(result.queryByText("Device Pools")).toBeNull();
expect(result.queryByText("Await Result")).toBeNull();
expect(result.queryByText("Result Timeout (Milliseconds)")).toBeNull();

// There's non-determinism in React Selects ids: react-select-X-live-region
// expect(result.container).toMatchSnapshot();
});

it("should render default enterprise fields for public workspace", async () => {
(useDependency as jest.Mock).mockReturnValue({
config: {
id: uuidv4(),
config: {
controlRoomUrl: "https://control.room.com",
},
},
service: {} as IService,
hasPermissions: true,
requestPermissions: jest.fn(),
});

const base = makeBaseState();
base.extension.blockPipeline[0].config.workspaceType = "public";
base.extension.blockPipeline[0].config.service = makeVariableExpression(
"@automationAnywhere"
);

const result = renderOptions(base);

expect(result.queryByText("Workspace")).not.toBeNull();
expect(result.queryByText("Bot")).not.toBeNull();
expect(result.queryByText("Run as Users")).not.toBeNull();
expect(result.queryByText("Device Pools")).not.toBeNull();
expect(result.queryByText("Await Result")).not.toBeNull();
expect(result.queryByText("Result Timeout (Milliseconds)")).toBeNull();

// There's non-determinism in React Selects ids: react-select-X-live-region
// expect(result.container).toMatchSnapshot();
});

it("should render result timeout", async () => {
(useDependency as jest.Mock).mockReturnValue({
config: {
id: uuidv4(),
config: {
controlRoomUrl: "https://control.room.com",
},
},
service: {} as IService,
hasPermissions: true,
requestPermissions: jest.fn(),
});

const base = makeBaseState();
base.extension.blockPipeline[0].config.awaitResult = true;
base.extension.blockPipeline[0].config.service = makeVariableExpression(
"@automationAnywhere"
);

const result = renderOptions(base);

expect(result.queryByText("Await Result")).not.toBeNull();
expect(result.queryByText("Result Timeout (Milliseconds)")).not.toBeNull();
});
});
Loading

0 comments on commit 18d7378

Please sign in to comment.