Skip to content

Commit

Permalink
[2.1.0] Introduce the DescribeView ACPS API (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
ctwomblyamzn authored Sep 22, 2023
1 parent 07fef97 commit bb64c47
Show file tree
Hide file tree
Showing 11 changed files with 1,893 additions and 9,883 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.1.0]
### Added
- The [DescribeView API](https://docs.aws.amazon.com/connect-participant/latest/APIReference/API_DescribeView.html)

## [2.0.2]
### Added
- Error message if API call is made before invoking session.connect(); #129
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,24 @@ const {

Gets the chat session details.

##### `chatSession.describeView()`
```js
const {
View
} = chatSession.describeView({
viewToken: "QVFJREFIaGIyaHZJWUZzNlVmMGVIY2NXdUVMMzdBTnprOGZkc3huRzhkSXR6eExOeXdGYTFwYitiOGRybklmMEZHbjBGZU1sQUFBQWJqQnNCZ2txaGtpRzl3MEJCd2FnWHpCZEFnRUFNRmdHQ1NxR1NJYjNEUUVIQVRBZUJnbGdoa2dCWlFNRUFTNHdFUVFNKys3ei9KTU41dG1BMWF4UkFnRVFnQ3NLckhKTEdXMUsyR1kvVHBUWWp0SmZxSG83VlcvOTg5WGZvckpMWDhOcGVJVHcrWUtnWjVwN3NxNGk6OlJ6STltam5rZjBiNENhOVdzM0wwaWxjR1dWUUxnb1Y1dmxNaEE5aGRkemZuV09hY0JEZFlpWFhpWnRPQlowNW9HT28xb0VnZ3JWV21aeWN0anhZZi9lOUdrYklSZVR5N2tpQmRRelFXSGpXZHpFSUExRCtvcWl5VGMzMzJoaWRldU5IaWwreEkvNmNmWUtpMXd5Qnh1aG0yY1AyWmk2byt2bTRDalFhWGxaM0Zrb1dqLy91aDVvRmtZcDY4UERuU0ZVQ1AyUU0zcjhNazI1ckZ2M0p6Z210bnMrSzVYY2VPN0xqWE1JMHZ0RE5uVEVYR1ZDcnc3SE82R0JITjV4NWporWGM9\\\", //REQUIRED
metadata: "foo" //OPTIONAL
}).data;
```
Wraps the [DescribeView](https://docs.aws.amazon.com/connect-participant/latest/APIReference/API_DescribeView.html) API.
The arguments are based on the [API model](https://docs.aws.amazon.com/connect-participant/latest/APIReference/API_DescribeView.html) with the following differences:
- All fields are in camelCase.
ChatJS automatically supplies the connectionToken via the session's internal data.
This api will only function after `chatSession.connect()` succeeds.
##### `agentChatSession.cleanUpOnParticipantDisconnect()`
```js
Expand Down
11,561 changes: 1,702 additions & 9,859 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "amazon-connect-chatjs",
"version": "2.0.2",
"version": "2.1.0",
"main": "dist/amazon-connect-chat.js",
"types": "dist/index.d.ts",
"engines": {
Expand Down
19 changes: 10 additions & 9 deletions src/client/aws-sdk-connectparticipant.js

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions src/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ class ChatClient {
createParticipantConnection(participantToken, type) {
throw new UnImplementedMethodException("createParticipantConnection in ChatClient");
}

describeView() {
throw new UnImplementedMethodException("describeView in ChatClient");
}
}
/*eslint-enable*/

Expand All @@ -86,6 +90,24 @@ class AWSChatClient extends ChatClient {
this.logger = LogManager.getLogger({ prefix: DEFAULT_PREFIX, logMetaData: args.logMetaData });
}

describeView(viewToken, connectionToken) {
let self = this;
let params = {
ViewToken: viewToken,
ConnectionToken: connectionToken
}
let describeViewRequest = self.chatClient.describeView(
params
);
return self._sendRequest(describeViewRequest).then((res) => {
self.logger.info("Successful describe view request")?.sendInternalLogToServer?.();
return res;
}).catch((err) => {
self.logger.error("describeView gave an error response", err)?.sendInternalLogToServer?.();
return Promise.reject(err);
});
}

createParticipantConnection(participantToken, type, acknowledgeConnection) {
let self = this;
var params = {
Expand Down
99 changes: 86 additions & 13 deletions src/client/client.spec.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { ChatClientFactory } from "./client";
import { CONTENT_TYPE } from "../constants";
import { jest } from "@jest/globals";

describe("client test cases", () => {
describe("event throttling test cases", () => {
const options = {};
const logMetaData = {};
const connectionToken = "connectionToken";
const content = "content";
const connectionToken = "connectionToken";
const content = "content";
const options = {};
const logMetaData = {};
var chatClient = ChatClientFactory.getCachedClient(options, logMetaData);

var chatClient = ChatClientFactory.getCachedClient(options, logMetaData);
beforeEach(() => {
jest.spyOn(chatClient, "_submitEvent").mockImplementation(() => {});
jest.spyOn(chatClient, "_sendRequest").mockResolvedValue({});
jest.useFakeTimers();
jest.clearAllTimers();
jest.clearAllMocks();
});

beforeEach(() => {
jest.spyOn(chatClient, "_submitEvent").mockImplementation(() => {});
jest.useFakeTimers();
jest.clearAllTimers();
jest.clearAllMocks();
});
describe("event throttling test cases", () => {

test("typing event should be throttled if content type is typing", () => {
let count = 0;
Expand Down Expand Up @@ -51,4 +51,77 @@ describe("client test cases", () => {
}
});
});

describe("Client Method Tests", () => {
beforeEach(() => {
jest.spyOn(chatClient, "_submitEvent").mockImplementation(() => {});
jest.spyOn(chatClient, "_sendRequest").mockResolvedValue({});
jest.useFakeTimers();
jest.clearAllTimers();
jest.clearAllMocks();
});
const options = {};
const logMetaData = {};
var chatClient = ChatClientFactory.getCachedClient(options, logMetaData);

describe("DescribeView", () => {
test("No errors thrown in happy case", async () => {
expect(chatClient.describeView("token", "type")).resolves.toEqual({});
});
test("Promise rejects in error case", async () => {
jest.spyOn(chatClient, "_sendRequest").mockRejectedValueOnce(new Error());
expect(chatClient.describeView("token", "type")).rejects.toThrow();
});
});
describe("CreateParticipantConnection", () => {
test("No errors thrown in happy case", async () => {
expect(chatClient.createParticipantConnection("token", "type", "acknowledgeConnection")).resolves.toEqual({});
});
test("Promise rejects in error case", async () => {
jest.spyOn(chatClient, "_sendRequest").mockRejectedValueOnce(new Error());
expect(chatClient.createParticipantConnection("token", "type", "acknowledgeConnection")).rejects.toThrow();
});
});

describe("DisconnectParticipant", () => {
test("No errors thrown in happy case", async () => {
expect(chatClient.disconnectParticipant("token")).resolves.toEqual({});
});
test("Promise rejects in error case", async () => {
jest.spyOn(chatClient, "_sendRequest").mockRejectedValueOnce(new Error());
expect(chatClient.disconnectParticipant("token")).rejects.toThrow();
});
});

describe("GetTranscript", () => {
const args = {
maxResults: "maxResults",
nextToken: "nextToken",
scanDirection: "scanDirection",
sortOrder: "sortOrder",
startPosition: {
id: "id",
absoluteTime: "absoluteTime",
mostRecent: "mostRecent",
},
}
test("No errors thrown in happy case", async () => {
expect(chatClient.getTranscript("token", args)).resolves.toEqual({});
});
test("Promise rejects in error case", async () => {
jest.spyOn(chatClient, "_sendRequest").mockRejectedValueOnce(new Error());
expect(chatClient.getTranscript("token", args)).rejects.toThrow();
});
});

describe("SendMessage", () => {
test("No errors thrown in happy case", async () => {
expect(chatClient.sendMessage("token", "content", "contentType")).resolves.toEqual({});
});
test("Promise rejects in error case", async () => {
jest.spyOn(chatClient, "_sendRequest").mockRejectedValueOnce(new Error());
expect(chatClient.sendMessage("token", "content", "contentType")).rejects.toThrow();
});
});
});
});
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const ACPS_METHODS = {
GET_TRANSCRIPT: "GetTranscript",
DISCONNECT_PARTICIPANT: "DisconnectParticipant",
CREATE_PARTICIPANT_CONNECTION: "CreateParticipantConnection",
DESCRIBE_VIEW: "DescribeView",
};

export const WEBSOCKET_EVENTS = {
Expand Down
12 changes: 11 additions & 1 deletion src/core/chatController.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,16 @@ class ChatController {
};
}

describeView(args) {
const startTime = new Date().getTime();
const metadata = args.metadata || null;
const connectionToken = this.connectionHelper.getConnectionToken();
return this.chatClient
.describeView(args.viewToken, connectionToken)
.then(this.handleRequestSuccess(metadata, ACPS_METHODS.DESCRIBE_VIEW, startTime))
.catch(this.handleRequestFailure(metadata, ACPS_METHODS.DESCRIBE_VIEW, startTime));
}

_convertConnectionHelperStatus(connectionHelperStatus) {
switch (connectionHelperStatus) {
case ConnectionHelperStatus.NeverStarted:
Expand All @@ -415,7 +425,7 @@ class ChatController {
connectionHelperStatus
));
}

getConnectionStatus() {
return this._convertConnectionHelperStatus(
this.connectionHelper.getStatus()
Expand Down
34 changes: 34 additions & 0 deletions src/core/chatController.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ describe("ChatController", () => {
disconnectParticipant: jest.fn(() => Promise.resolve({ testField: "test" })),
sendAttachment: jest.fn(() => Promise.resolve({ testField: "test" })),
downloadAttachment: jest.fn(() => Promise.resolve({ testField: "test" })),
describeView: jest.fn(() => Promise.resolve({ testField: "test" })),
};
jest.spyOn(csmService, 'addLatencyMetricWithStartTime').mockImplementation(() => {});
jest.spyOn(csmService, 'addCountAndErrorMetric').mockImplementation(() => {});
Expand Down Expand Up @@ -358,6 +359,39 @@ describe("ChatController", () => {
}
});

test("describeView works as expected", async () => {
const args = {
metadata: "metadata",
viewToken: "viewToken",
};
const chatController = getChatController();
await chatController.connect();
const response = await chatController.describeView(args);
expect(chatClient.describeView).toHaveBeenCalledWith("viewToken", "token");
expect(response.metadata).toBe("metadata");
expect(response.testField).toBe("test");
expect(csmService.addCountAndErrorMetric).toHaveBeenCalledWith(ACPS_METHODS.DESCRIBE_VIEW, CSM_CATEGORY.API, false, []);
expect(csmService.addLatencyMetricWithStartTime).toHaveBeenCalledWith(ACPS_METHODS.DESCRIBE_VIEW, expect.anything(), CSM_CATEGORY.API, []);
});

test("describeView throws an error", async () => {
const args = {
metadata: "metadata",
viewToken: "viewToken",
};
const chatController = getChatController();
await chatController.connect();
chatClient.describeView = jest.fn(() => Promise.reject({}));
try {
await chatController.describeView(args);
expect(false).toEqual(true);
} catch (e) {
expect(e.metadata).toEqual("metadata");
expect(csmService.addCountAndErrorMetric).toHaveBeenCalledWith(ACPS_METHODS.DESCRIBE_VIEW, CSM_CATEGORY.API, true, []);
expect(csmService.addLatencyMetricWithStartTime).toHaveBeenCalledWith(ACPS_METHODS.DESCRIBE_VIEW, expect.anything(), CSM_CATEGORY.API, []);
}
});

test("getTranscript works as expected", async () => {
var args = {
metadata: "metadata",
Expand Down
4 changes: 4 additions & 0 deletions src/core/chatSession.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ export class ChatSession {
getChatDetails() {
return this.controller.getChatDetails();
}

describeView(args) {
return this.controller.describeView(args);
}
}

class AgentChatSession extends ChatSession {
Expand Down

0 comments on commit bb64c47

Please sign in to comment.