├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── EXTENDING.md ├── LICENSE ├── NOTICE ├── README.md ├── architecture ├── Architecture.drawio ├── Architecture.png └── Integration.drawio ├── connect └── contactflows │ ├── RulesEngineAgentWhisper.json │ ├── RulesEngineBootstrap.json │ ├── RulesEngineCallback.json │ ├── RulesEngineCustomerHold.json │ ├── RulesEngineCustomerQueue.json │ ├── RulesEngineCustomerWhisper.json │ ├── RulesEngineDTMFInput.json │ ├── RulesEngineDTMFMenu.json │ ├── RulesEngineDisconnect.json │ ├── RulesEngineError.json │ ├── RulesEngineExternalNumber.json │ ├── RulesEngineIntegration.json │ ├── RulesEngineMain.json │ ├── RulesEngineMessage.json │ ├── RulesEngineNLUInput.json │ ├── RulesEngineNLUMenu.json │ ├── RulesEngineOutboundWhisper.json │ ├── RulesEngineQueue.json │ ├── RulesEngineRuleSet.json │ ├── RulesEngineSMSMessage.json │ ├── RulesEngineTerminate.json │ ├── RulesEngineWait.json │ ├── empty_agent_whisper_flow.json │ ├── empty_customer_hold_flow.json │ ├── empty_customer_queue_flow.json │ ├── empty_customer_whisper_flow.json │ ├── empty_flow.json │ └── empty_outbound_whisper_flow.json ├── data ├── keepwarm │ ├── dev-keepwarm.json │ └── example-keepwarm.json ├── timezone.json └── users │ ├── .gitignore │ └── example-admin.json ├── env └── example.sh ├── lambda ├── BackupRulesetsAndTests.js ├── BatchInferenceRunner.js ├── BatchInferenceStart.js ├── CacheConnectData.js ├── CloneRule.js ├── CloneRuleSet.js ├── ConnectCheckTimeout.js ├── ConnectCreateCallHistory.js ├── ConnectCreateCallback.js ├── ConnectCustomerQueue.js ├── ConnectDTMFInput.js ├── ConnectDTMFMenu.js ├── ConnectDeleteCallback.js ├── ConnectGetCallbackStatus.js ├── ConnectIntegrationStart.js ├── ConnectLoadState.js ├── ConnectNLUInput.js ├── ConnectNLUMenu.js ├── ConnectRulesInference.js ├── ConnectSendSMS.js ├── ConnectUpdateState.js ├── ContactEventListener.js ├── CopyTests.js ├── CreateContactFlow.js ├── CreateEndPoint.js ├── CreateHoliday.js ├── CreateRule.js ├── CreateRuleSet.js ├── CreateTest.js ├── CreateUser.js ├── CreateWeight.js ├── DeleteObject.js ├── DescribeLexBot.js ├── GetBatches.js ├── GetConnectData.js ├── GetEndPoints.js ├── GetHolidays.js ├── GetLastChange.js ├── GetRule.js ├── GetRuleSets.js ├── GetRuleSetsForCSVExport.js ├── GetRuleSetsForExport.js ├── GetRuleSetsGraph.js ├── GetSystemHealth.js ├── GetTests.js ├── GetUsers.js ├── ImportRuleSets.js ├── ImportTests.js ├── InferenceAPI.js ├── InteractiveInference.js ├── KeepWarm.js ├── LexFulfillment.js ├── MoveRuleSets.js ├── MoveTests.js ├── PostImportRuleSets.js ├── PostImportTests.js ├── RefreshConnectCache.js ├── RenameRule.js ├── RenameRuleSet.js ├── SaveHoliday.js ├── UpdateContactFlow.js ├── UpdateEndPoint.js ├── UpdateRule.js ├── UpdateRuleSet.js ├── UpdateTest.js ├── UpdateUser.js ├── UpdateWeight.js ├── VerifyLogin.js ├── integration │ └── IntegrationEcho.js ├── interactive │ ├── DTMFInput.js │ ├── DTMFMenu.js │ ├── Distribution.js │ ├── ExternalNumber.js │ ├── Integration.js │ ├── Message.js │ ├── Metric.js │ ├── NLUInput.js │ ├── NLUMenu.js │ ├── Queue.js │ ├── RuleSet.js │ ├── SMSMessage.js │ ├── SetAttributes.js │ ├── Terminate.js │ ├── TextInference.js │ ├── UpdateStates.js │ └── Wait.js └── utils │ ├── BackoffUtils.js │ ├── CloudWatchUtils.js │ ├── CommonUtils.js │ ├── ConfigUtils.js │ ├── ConnectUtils.js │ ├── DynamoUtils.js │ ├── ErrorCodeUtils.js │ ├── HandlebarsUtils.js │ ├── InferenceUtils.js │ ├── KeepWarmUtils.js │ ├── LambdaUtils.js │ ├── LexUtils.js │ ├── MockUtils.js │ ├── OperatingHoursUtils.js │ ├── PinpointUtils.js │ ├── PollyUtils.js │ ├── RequestUtils.js │ ├── RulesEngine.js │ ├── S3Utils.js │ └── SNSUtils.js ├── lex ├── .gitignore ├── bots │ ├── date.json │ ├── intent.json │ ├── number.json │ ├── phone.json │ ├── slots.json │ ├── time.json │ └── yesno.json ├── build_all.sh ├── build_one.sh ├── delete_all.sh ├── delete_lex_bot.js ├── delete_one.sh ├── deploy_lex_bot.js ├── package-lock.json └── package.json ├── manual ├── admin.png ├── architecture.png ├── error_weight.png ├── graph.png ├── graph_filtered.png ├── health_loading.png ├── healthy.png ├── holidays.png ├── home.png ├── home_login.png ├── integration_architecture.png ├── integration_rule.png ├── loading.png ├── login.png ├── pipeline-setup1.png ├── pipeline-setup2.png ├── pipeline-setup2a.png ├── pipeline-setup2b.png ├── pipeline-setup3.png ├── pipeline-setup3a.png ├── pipeline-setup3b.png ├── repair.png ├── repaired.png ├── repairing.png ├── rule_weights.png ├── rulesets.png ├── training_installation.png ├── training_lex.png └── training_weights.png ├── package-lock.json ├── package.json ├── pipeline-setup.md ├── scripts ├── rules_engine_check_aws_account.sh ├── rules_engine_create_deployment_bucket.sh ├── rules_engine_extract_flows.sh ├── rules_engine_filter_flows.js ├── rules_engine_function_deploy.sh ├── rules_engine_prepare_fixes.js ├── rules_engine_s3_clean.sh ├── rules_engine_s3_deploy.sh └── rules_engine_serverless_deploy.sh ├── serverless.yaml ├── test ├── BackoffUtilsTests.js ├── BatchInferenceRunnerTest.js ├── CommonUtilsTest.js ├── ConnectCustomerQueueTest.js ├── ConnectDTMFMenuTest.js ├── ConnectNLUInputTest.js ├── ConnectNLUMenuTest.js ├── ConnectUtilsTest.js ├── GenericRequireLoaderTest.js ├── InferenceUtilsTest.js ├── KeepWarmUtilsTest.js ├── LexFulfillmentTest.js ├── LexUtilsTest.js ├── RulesEngineFunctionTest.js ├── VerifyLoginTests.js ├── integration │ └── IntegrationEchoTest.js ├── interactive │ ├── DTMFInputTest.js │ ├── DTMFMenuTest.js │ ├── DistributionTest.js │ ├── ExternalNumberTest.js │ ├── IntegrationTest.js │ ├── InteractiveConfig.js │ ├── MessageTest.js │ ├── MetricTest.js │ ├── NLUInputTest.js │ ├── NLUMenuTest.js │ ├── QueueTest.js │ ├── RuleSetTest.js │ ├── SMSMessageTest.js │ ├── SetAttributesTest.js │ ├── TerminateTest.js │ ├── TextInferenceTest.js │ └── UpdateStatesTest.js └── utils │ ├── DynamoStateTableMocker.js │ ├── DynamoTableDataMocker.js │ ├── LambdaMocker.js │ ├── LexRuntimeV2Mocker.js │ ├── MockEventGenerator.js │ └── config.js └── web ├── config └── .gitignore ├── css └── site.css ├── docs ├── INS207_Attribute_Routing.pdf └── INS207_Attribute_Routing.png ├── img ├── architecture.png ├── aws.png ├── favicon.png ├── help │ ├── integration_architecture.png │ ├── rules_engine_architecture.png │ ├── training_installation.png │ ├── training_lex.png │ └── training_weights.png ├── icons │ ├── admin.png │ ├── agent.png │ ├── apigateway.png │ ├── aws.png │ ├── cloudfront.png │ ├── cloudwatch.png │ ├── computer.png │ ├── connect_blue.png │ ├── connect_red.png │ ├── customer.png │ ├── customers.png │ ├── dynamodb.png │ ├── fire-users.png │ ├── lambda.png │ ├── phone.png │ ├── queue.png │ ├── ruleset.png │ ├── ruleset_unselected.png │ ├── s3.png │ ├── terminate.png │ ├── users.png │ └── users_blue.png ├── loading.gif ├── loading_small.gif ├── logo.png ├── progress_blue.png ├── progress_green.png ├── progress_grey.png ├── progress_red.png └── progress_yellow.png ├── index.html ├── js └── app.js └── templates ├── admin.hbs ├── batchResult.hbs ├── batchResultSave.hbs ├── batchResults.hbs ├── configure.hbs ├── configureRule.hbs ├── configureRuleSet.hbs ├── endpoints.hbs ├── graph.hbs ├── help.hbs ├── holidays.hbs ├── home.hbs ├── interact.hbs ├── login.hbs ├── logout.hbs ├── navigation.hbs └── test.hbs /.gitignore: -------------------------------------------------------------------------------- 1 | .serverless 2 | node_modules 3 | .DS_Store 4 | assets 5 | env 6 | build 7 | temp 8 | web/config/site_config.json 9 | .nyc_output 10 | mocha.json 11 | gitcommit.sh 12 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /architecture/Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/architecture/Architecture.png -------------------------------------------------------------------------------- /connect/contactflows/RulesEngineCallback.json: -------------------------------------------------------------------------------- 1 | { 2 | "Actions": [ 3 | { 4 | "Identifier": "14461009-7fd8-48b3-a209-1c0a2686ab7c", 5 | "Parameters": { 6 | "FlowLoggingBehavior": "Enabled" 7 | }, 8 | "Transitions": { 9 | "Conditions": [], 10 | "Errors": [], 11 | "NextAction": "76c95b63-e1f5-4901-b488-d350f47d35c4" 12 | }, 13 | "Type": "UpdateFlowLoggingBehavior" 14 | }, 15 | { 16 | "Identifier": "76c95b63-e1f5-4901-b488-d350f47d35c4", 17 | "Parameters": {}, 18 | "Transitions": {}, 19 | "Type": "DisconnectParticipant" 20 | } 21 | ], 22 | "Metadata": { 23 | "ActionMetadata": { 24 | "14461009-7fd8-48b3-a209-1c0a2686ab7c": { 25 | "position": { 26 | "x": 200, 27 | "y": 55 28 | } 29 | }, 30 | "76c95b63-e1f5-4901-b488-d350f47d35c4": { 31 | "position": { 32 | "x": 491, 33 | "y": 146 34 | } 35 | } 36 | }, 37 | "entryPointPosition": { 38 | "x": 15, 39 | "y": 20 40 | }, 41 | "snapToGrid": false 42 | }, 43 | "StartAction": "14461009-7fd8-48b3-a209-1c0a2686ab7c", 44 | "Version": "2019-10-30" 45 | } 46 | -------------------------------------------------------------------------------- /connect/contactflows/RulesEngineCustomerHold.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2019-10-30", 3 | "StartAction": "d148935c-615d-4420-b21f-9ad59e969255", 4 | "Metadata": { 5 | "entryPointPosition": { 6 | "x": 20, 7 | "y": 20 8 | }, 9 | "snapToGrid": false, 10 | "ActionMetadata": { 11 | "d148935c-615d-4420-b21f-9ad59e969255": { 12 | "position": { 13 | "x": 269, 14 | "y": 75 15 | }, 16 | "audio": [ 17 | { 18 | "type": "Text", 19 | "ttsType": "text", 20 | "$$hashKey": "object:96", 21 | "tts": "Hi. We will be back with you shortly." 22 | } 23 | ], 24 | "timeoutUnit": { 25 | "display": "Minutes", 26 | "value": "min" 27 | } 28 | } 29 | } 30 | }, 31 | "Actions": [ 32 | { 33 | "Identifier": "d148935c-615d-4420-b21f-9ad59e969255", 34 | "Parameters": { 35 | "Messages": [ 36 | { 37 | "Text": "Hi. We will be back with you shortly." 38 | } 39 | ] 40 | }, 41 | "Transitions": { 42 | "Errors": [], 43 | "Conditions": [] 44 | }, 45 | "Type": "MessageParticipantIteratively" 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /connect/contactflows/RulesEngineError.json: -------------------------------------------------------------------------------- 1 | { 2 | "Actions": [ 3 | { 4 | "Identifier": "8f93684e-595d-490b-bb93-1bd36fb90171", 5 | "Parameters": { 6 | "Text": "I am sorry, I have encountered an error. Good-bye." 7 | }, 8 | "Transitions": { 9 | "Conditions": [], 10 | "Errors": [], 11 | "NextAction": "76c95b63-e1f5-4901-b488-d350f47d35c4" 12 | }, 13 | "Type": "MessageParticipant" 14 | }, 15 | { 16 | "Identifier": "76c95b63-e1f5-4901-b488-d350f47d35c4", 17 | "Parameters": {}, 18 | "Transitions": {}, 19 | "Type": "DisconnectParticipant" 20 | } 21 | ], 22 | "Metadata": { 23 | "ActionMetadata": { 24 | "76c95b63-e1f5-4901-b488-d350f47d35c4": { 25 | "position": { 26 | "x": 559, 27 | "y": 144 28 | } 29 | }, 30 | "8f93684e-595d-490b-bb93-1bd36fb90171": { 31 | "position": { 32 | "x": 253, 33 | "y": 53 34 | }, 35 | "useDynamic": false 36 | } 37 | }, 38 | "entryPointPosition": { 39 | "x": 15, 40 | "y": 20 41 | }, 42 | "snapToGrid": false 43 | }, 44 | "StartAction": "8f93684e-595d-490b-bb93-1bd36fb90171", 45 | "Version": "2019-10-30" 46 | } 47 | -------------------------------------------------------------------------------- /connect/contactflows/RulesEngineMain.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2019-10-30", 3 | "StartAction": "71c4f8b5-26c4-4f0d-9ff4-d1c0f2010f3f", 4 | "Metadata": { 5 | "entryPointPosition": { 6 | "x": 103, 7 | "y": 26 8 | }, 9 | "snapToGrid": false, 10 | "ActionMetadata": { 11 | "ee5a4707-3731-4aaa-9120-93a0c85fc55b": { 12 | "position": { 13 | "x": 1258, 14 | "y": 350 15 | } 16 | }, 17 | "4a81da48-58d7-4a40-b513-c044097b1e66": { 18 | "position": { 19 | "x": 978, 20 | "y": 265 21 | }, 22 | "useDynamic": false, 23 | "ContactFlow": { 24 | "id": "{{contactFlows.RulesEngineError.arn}}", 25 | "text": "RulesEngineError" 26 | } 27 | }, 28 | "e74f53e8-dc7b-4d1c-9a82-4c5013797a4f": { 29 | "position": { 30 | "x": 715, 31 | "y": 112 32 | }, 33 | "useDynamic": true 34 | }, 35 | "71c4f8b5-26c4-4f0d-9ff4-d1c0f2010f3f": { 36 | "position": { 37 | "x": 369, 38 | "y": 42 39 | }, 40 | "dynamicMetadata": {}, 41 | "useDynamic": false 42 | } 43 | } 44 | }, 45 | "Actions": [ 46 | { 47 | "Identifier": "ee5a4707-3731-4aaa-9120-93a0c85fc55b", 48 | "Type": "DisconnectParticipant", 49 | "Parameters": {}, 50 | "Transitions": {} 51 | }, 52 | { 53 | "Identifier": "4a81da48-58d7-4a40-b513-c044097b1e66", 54 | "Parameters": { 55 | "ContactFlowId": "{{contactFlows.RulesEngineError.arn}}" 56 | }, 57 | "Transitions": { 58 | "NextAction": "ee5a4707-3731-4aaa-9120-93a0c85fc55b", 59 | "Errors": [ 60 | { 61 | "NextAction": "ee5a4707-3731-4aaa-9120-93a0c85fc55b", 62 | "ErrorType": "NoMatchingError" 63 | } 64 | ], 65 | "Conditions": [] 66 | }, 67 | "Type": "TransferToFlow" 68 | }, 69 | { 70 | "Identifier": "e74f53e8-dc7b-4d1c-9a82-4c5013797a4f", 71 | "Parameters": { 72 | "ContactFlowId": "$.External.CurrentRule_nextFlowArn" 73 | }, 74 | "Transitions": { 75 | "NextAction": "4a81da48-58d7-4a40-b513-c044097b1e66", 76 | "Errors": [ 77 | { 78 | "NextAction": "4a81da48-58d7-4a40-b513-c044097b1e66", 79 | "ErrorType": "NoMatchingError" 80 | } 81 | ], 82 | "Conditions": [] 83 | }, 84 | "Type": "TransferToFlow" 85 | }, 86 | { 87 | "Identifier": "71c4f8b5-26c4-4f0d-9ff4-d1c0f2010f3f", 88 | "Parameters": { 89 | "LambdaFunctionARN": "{{lambdaFunctions.connectrulesinference.arn}}", 90 | "InvocationTimeLimitSeconds": "7" 91 | }, 92 | "Transitions": { 93 | "NextAction": "e74f53e8-dc7b-4d1c-9a82-4c5013797a4f", 94 | "Errors": [ 95 | { 96 | "NextAction": "4a81da48-58d7-4a40-b513-c044097b1e66", 97 | "ErrorType": "NoMatchingError" 98 | } 99 | ], 100 | "Conditions": [] 101 | }, 102 | "Type": "InvokeLambdaFunction" 103 | } 104 | ] 105 | } 106 | -------------------------------------------------------------------------------- /connect/contactflows/RulesEngineTerminate.json: -------------------------------------------------------------------------------- 1 | { 2 | "Actions": [ 3 | { 4 | "Identifier": "76c95b63-e1f5-4901-b488-d350f47d35c4", 5 | "Parameters": {}, 6 | "Transitions": {}, 7 | "Type": "DisconnectParticipant" 8 | } 9 | ], 10 | "Metadata": { 11 | "ActionMetadata": { 12 | "76c95b63-e1f5-4901-b488-d350f47d35c4": { 13 | "position": { 14 | "x": 243, 15 | "y": 70 16 | } 17 | } 18 | }, 19 | "entryPointPosition": { 20 | "x": 15, 21 | "y": 20 22 | }, 23 | "snapToGrid": false 24 | }, 25 | "StartAction": "76c95b63-e1f5-4901-b488-d350f47d35c4", 26 | "Version": "2019-10-30" 27 | } 28 | -------------------------------------------------------------------------------- /connect/contactflows/empty_agent_whisper_flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2019-10-30", 3 | "StartAction": "d52ec4ae-d717-4a80-a8f5-02b75b1942ca", 4 | "Metadata": { 5 | "entryPointPosition": { 6 | "x": 80, 7 | "y": 34 8 | }, 9 | "snapToGrid": false, 10 | "ActionMetadata": { 11 | "d52ec4ae-d717-4a80-a8f5-02b75b1942ca": { 12 | "position": { 13 | "x": 313, 14 | "y": 92 15 | } 16 | } 17 | } 18 | }, 19 | "Actions": [ 20 | { 21 | "Identifier": "d52ec4ae-d717-4a80-a8f5-02b75b1942ca", 22 | "Parameters": {}, 23 | "Transitions": {}, 24 | "Type": "EndFlowExecution" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /connect/contactflows/empty_customer_hold_flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2019-10-30", 3 | "StartAction": "7f66b57f-13d6-4bef-9427-870751824c86", 4 | "Metadata": { 5 | "entryPointPosition": { 6 | "x": 20, 7 | "y": 20 8 | }, 9 | "snapToGrid": false, 10 | "ActionMetadata": { 11 | "7f66b57f-13d6-4bef-9427-870751824c86": { 12 | "position": { 13 | "x": 241, 14 | "y": 72 15 | }, 16 | "audio": [ 17 | { 18 | "type": "Text", 19 | "ttsType": "text", 20 | "$$hashKey": "object:84", 21 | "tts": "This is a placeholder hold message" 22 | } 23 | ], 24 | "timeoutUnit": { 25 | "display": "Minutes", 26 | "value": "min" 27 | } 28 | } 29 | } 30 | }, 31 | "Actions": [ 32 | { 33 | "Identifier": "7f66b57f-13d6-4bef-9427-870751824c86", 34 | "Parameters": { 35 | "Messages": [ 36 | { 37 | "Text": "This is a placeholder hold message" 38 | } 39 | ] 40 | }, 41 | "Transitions": { 42 | "Errors": [], 43 | "Conditions": [] 44 | }, 45 | "Type": "MessageParticipantIteratively" 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /connect/contactflows/empty_customer_queue_flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2019-10-30", 3 | "StartAction": "492f0fa7-7b2e-4786-8427-8cf6e81fa399", 4 | "Metadata": { 5 | "entryPointPosition": { 6 | "x": 20, 7 | "y": 20 8 | }, 9 | "snapToGrid": false, 10 | "ActionMetadata": { 11 | "492f0fa7-7b2e-4786-8427-8cf6e81fa399": { 12 | "position": { 13 | "x": 236, 14 | "y": 75 15 | } 16 | } 17 | } 18 | }, 19 | "Actions": [ 20 | { 21 | "Identifier": "492f0fa7-7b2e-4786-8427-8cf6e81fa399", 22 | "Parameters": {}, 23 | "Transitions": {}, 24 | "Type": "EndFlowExecution" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /connect/contactflows/empty_customer_whisper_flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2019-10-30", 3 | "StartAction": "c8e54e5f-4f29-41a3-87fb-b37064d4a148", 4 | "Metadata": { 5 | "entryPointPosition": { 6 | "x": 20, 7 | "y": 20 8 | }, 9 | "snapToGrid": false, 10 | "ActionMetadata": { 11 | "c8e54e5f-4f29-41a3-87fb-b37064d4a148": { 12 | "position": { 13 | "x": 252, 14 | "y": 70 15 | } 16 | } 17 | } 18 | }, 19 | "Actions": [ 20 | { 21 | "Identifier": "c8e54e5f-4f29-41a3-87fb-b37064d4a148", 22 | "Parameters": {}, 23 | "Transitions": {}, 24 | "Type": "EndFlowExecution" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /connect/contactflows/empty_flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "Actions": [ 3 | { 4 | "Identifier": "14461009-7fd8-48b3-a209-1c0a2686ab7c", 5 | "Parameters": { 6 | "FlowLoggingBehavior": "Enabled" 7 | }, 8 | "Transitions": { 9 | "Conditions": [], 10 | "Errors": [], 11 | "NextAction": "76c95b63-e1f5-4901-b488-d350f47d35c4" 12 | }, 13 | "Type": "UpdateFlowLoggingBehavior" 14 | }, 15 | { 16 | "Identifier": "76c95b63-e1f5-4901-b488-d350f47d35c4", 17 | "Parameters": {}, 18 | "Transitions": {}, 19 | "Type": "DisconnectParticipant" 20 | } 21 | ], 22 | "Metadata": { 23 | "ActionMetadata": { 24 | "14461009-7fd8-48b3-a209-1c0a2686ab7c": { 25 | "position": { 26 | "x": 200, 27 | "y": 55 28 | } 29 | }, 30 | "76c95b63-e1f5-4901-b488-d350f47d35c4": { 31 | "position": { 32 | "x": 491, 33 | "y": 146 34 | } 35 | } 36 | }, 37 | "entryPointPosition": { 38 | "x": 15, 39 | "y": 20 40 | }, 41 | "snapToGrid": false 42 | }, 43 | "StartAction": "14461009-7fd8-48b3-a209-1c0a2686ab7c", 44 | "Version": "2019-10-30" 45 | } -------------------------------------------------------------------------------- /connect/contactflows/empty_outbound_whisper_flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2019-10-30", 3 | "StartAction": "3cac3989-c5f6-4fbd-870f-79ca7cf34a7d", 4 | "Metadata": { 5 | "entryPointPosition": { 6 | "x": 20, 7 | "y": 20 8 | }, 9 | "snapToGrid": false, 10 | "ActionMetadata": { 11 | "3cac3989-c5f6-4fbd-870f-79ca7cf34a7d": { 12 | "position": { 13 | "x": 305, 14 | "y": 75 15 | } 16 | } 17 | } 18 | }, 19 | "Actions": [ 20 | { 21 | "Identifier": "3cac3989-c5f6-4fbd-870f-79ca7cf34a7d", 22 | "Parameters": {}, 23 | "Transitions": {}, 24 | "Type": "EndFlowExecution" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /data/keepwarm/dev-keepwarm.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConfigKey": {"S": "KeepWarm"}, 3 | "LastUpdate": {"S": "2021-07-16T00:00:00Z"}, 4 | "ConfigData": {"S": "[{\"Name\": \"connectrulesinference\", \"Count\": 1},{\"Name\": \"inferenceapi\", \"Count\": 1},{\"Name\": \"connectintegrationstart\", \"Count\": 1},{\"Name\": \"connectnluinput\", \"Count\": 1},{\"Name\": \"connectnlumenu\", \"Count\": 1}]"} 5 | } 6 | -------------------------------------------------------------------------------- /data/keepwarm/example-keepwarm.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConfigKey": {"S": "KeepWarm"}, 3 | "LastUpdate": {"S": "2021-07-16T00:00:00Z"}, 4 | "ConfigData": {"S": "[{\"Name\": \"connectrulesinference\", \"Count\": 1},{\"Name\": \"inferenceapi\", \"Count\": 1},{\"Name\": \"connectintegrationstart\", \"Count\": 1},{\"Name\": \"connectnluinput\", \"Count\": 1},{\"Name\": \"connectnlumenu\", \"Count\": 1}]"} 5 | } 6 | -------------------------------------------------------------------------------- /data/timezone.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConfigKey": {"S": "CallCentreTimeZone"}, 3 | "ConfigData": {"S": "Australia/Melbourne"}, 4 | "LastUpdate": {"S": "2021-07-01T00:00:00Z"} 5 | } -------------------------------------------------------------------------------- /data/users/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | -------------------------------------------------------------------------------- /data/users/example-admin.json: -------------------------------------------------------------------------------- 1 | { 2 | "UserId": {"S": "89b1cc76-3ad6-11ec-97b9-c353ea8f11eb"}, 3 | "APIKey": {"S": "CHANGEME"}, 4 | "FirstName": {"S": "Admin"}, 5 | "LastName": {"S": "User"}, 6 | "EmailAddress": {"S": "admin@connectrules.com"}, 7 | "UserEnabled": {"S": "true"}, 8 | "UserRole": {"S": "ADMINISTRATOR"} 9 | } 10 | -------------------------------------------------------------------------------- /env/example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ------------------------------------------------ 4 | # 5 | # !README! 6 | # 7 | # Copy this file and name it dev.sh or something 8 | # that matches your stage below 9 | # 10 | # Replace TBA with valid values for your environment 11 | # 12 | # ------------------------------------------------ 13 | 14 | # Change to test, uat, prod etc 15 | export stage=dev 16 | 17 | # Should not need to change 18 | export service=rules-engine 19 | 20 | # The name of your enviroment shown in the site banner 21 | export environmentName="TBA $stage" 22 | 23 | # Target AWS deployment region 24 | export region=ap-southeast-2 25 | 26 | # AWS account number 27 | export accountNumber=TBA 28 | 29 | # Amazon Connect Instance Id 30 | export instanceId=TBA 31 | 32 | # Lex bot locale for inferencing (must match deployed bot settings) 33 | export botLocaleId=en_AU 34 | 35 | # Polly settings during interactive inferencing 36 | export voiceId=Olivia 37 | export voiceLanguage=en-AU 38 | 39 | # Batch size for batch testing concurrency 40 | export batchSize=10 41 | 42 | # ------------------------------------------------ 43 | # 44 | # !README! 45 | # 46 | # If required, specify a local named AWS profile to use 47 | # and uncomment these lines. 48 | # 49 | # This will be used by the AWS CLI and Node.js deployment helpers. 50 | # 51 | # Alternatively, export AWS credentials through your CICD 52 | # tooling or do nothing if running with an IAM role context 53 | # (for example from a CodeBuild job) 54 | # 55 | # ------------------------------------------------ 56 | 57 | # export profile=TBA 58 | # export AWS_PROFILE=$profile 59 | # export AWS_SDK_LOAD_CONFIG="true" 60 | 61 | # Amazon Connect Instance ARN 62 | export instanceArn="arn:aws:connect:${region}:${accountNumber}:instance/${instanceId}" 63 | 64 | # S3 bucket to upload deployment assets to 65 | export deploymentBucket="${stage}-${service}-deployment-${accountNumber}" 66 | 67 | # Lex conversational logs bucket (created during serverless deployment) 68 | export conversationalLogsBucketArn="arn:aws:s3:::${stage}-${service}-lex-${region}-${accountNumber}" 69 | 70 | # Lex role (created during serverless deployment) 71 | export lexRoleArn="arn:aws:iam::${accountNumber}:role/${stage}-${service}-${region}-lexrole" 72 | 73 | echo "Exported $environmentName" 74 | -------------------------------------------------------------------------------- /lambda/BackupRulesetsAndTests.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const s3Utils = require('./utils/S3Utils'); 5 | const dynamoUtils = require('./utils/DynamoUtils'); 6 | const moment = require('moment'); 7 | 8 | /** 9 | * Backs up rule sets and tests 10 | */ 11 | exports.handler = async (event) => 12 | { 13 | const rulesets = await getRuleSetsToBackup(); 14 | await uploadAndBackup(rulesets, process.env.BACKUP_BUCKET_NAME, 'ruleset-backup.json'); 15 | 16 | const tests = await getTestsToBackup(); 17 | await uploadAndBackup(tests, process.env.BACKUP_BUCKET_NAME, 'test-backup.json'); 18 | 19 | console.info('Successfully backed up rule sets and tests'); 20 | 21 | return { 22 | success: true 23 | }; 24 | }; 25 | 26 | 27 | /** 28 | * Fetches rule sets to backup 29 | */ 30 | const getRuleSetsToBackup = async () => 31 | { 32 | const ruleSets = await dynamoUtils.getRuleSetsAndRules(process.env.RULE_SETS_TABLE, process.env.RULES_TABLE); 33 | 34 | // Remove the rule set ids, rule ids and weight ids from the response 35 | ruleSets.forEach(ruleSet => 36 | { 37 | ruleSet.ruleSetId = undefined; 38 | ruleSet.rules.forEach(rule => 39 | { 40 | rule.ruleId = undefined; 41 | rule.ruleSetId = undefined; 42 | rule.weights.forEach(weight => 43 | { 44 | weight.weightId = undefined; 45 | }); 46 | }); 47 | }); 48 | 49 | return ruleSets; 50 | }; 51 | 52 | /** 53 | * Fetches tests to backup 54 | */ 55 | const getTestsToBackup = async () => 56 | { 57 | const tests = await dynamoUtils.getTests(process.env.TESTS_TABLE); 58 | 59 | // Remove the test ids 60 | tests.forEach(test => 61 | { 62 | test.testId = undefined; 63 | }); 64 | 65 | var testExport = { 66 | type: 'RulesEngineTests', 67 | version: process.env.VERSION, 68 | environmentName: process.env.ENVIRONMENT_NAME, 69 | format: +process.env.TEST_EXPORT_FORMAT, 70 | exportedBy: 'scheduled export', 71 | exportedAt: moment().utc().format(), 72 | testCount: tests.length, 73 | tests: tests 74 | }; 75 | 76 | return testExport; 77 | }; 78 | 79 | /** 80 | * Upload data as JSON to S3 81 | */ 82 | const uploadAndBackup = async (data, bucketName, suffix) => 83 | { 84 | try 85 | { 86 | // Upload it to /year/month/day/[hour_of_day]-[suffix] 87 | const now = moment().utc(); 88 | const year = now.year(); 89 | const month = now.month() + 1; 90 | const day = now.date(); 91 | const timeStamp = now.format('YYYY-MM-DD-THH-mm-ssZ'); 92 | const key = `backups/${year}/${month}/${day}/${timeStamp}-${suffix}`; 93 | 94 | console.info(`Backing up data to: s3://${bucketName}/${key}`); 95 | 96 | await s3Utils.putObject(bucketName, key, JSON.stringify(data, null, 2)); 97 | } 98 | catch (error) 99 | { 100 | console.error('Failed to backup to S3', error); 101 | throw error; 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /lambda/CacheConnectData.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var connectUtils = require('./utils/ConnectUtils.js'); 6 | var dynamoUtils = require('./utils/DynamoUtils.js'); 7 | 8 | var moment = require('moment'); 9 | 10 | /** 11 | * Caches all Amazon Connect data in DynamoDB, triggered via CloudWatch CRON 12 | */ 13 | exports.handler = async(event, context) => 14 | { 15 | try 16 | { 17 | requestUtils.logRequest(event); 18 | 19 | await connectUtils.cacheConnectData(process.env.STAGE, process.env.SERVICE, 20 | process.env.REGION, process.env.ACCOUNT_NUMBER, process.env.INSTANCE_ID, 21 | process.env.BOT_ALIAS, process.env.BOT_LOCALE_ID, 22 | process.env.CONFIG_TABLE); 23 | 24 | console.log('[INFO] fetched and cached all Connect data into DynamoDB'); 25 | 26 | return { 27 | success: true 28 | } 29 | } 30 | catch (error) 31 | { 32 | console.log('[ERROR] failed to fetch and cache Connect config data into DynamoDB', error); 33 | throw error; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /lambda/CloneRule.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Clone rule from existing and Creates a new rule in DynamoDB 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | const CONFLICT_HTTP_ERROR_CODE = 409; 18 | var user = await requestUtils.verifyAPIKey(event); 19 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 20 | 21 | var body = JSON.parse(event.body); 22 | var {ruleSetId, ruleId, ruleName} = body; 23 | 24 | // Check for an existing rule with this name in this rule set and fail if it exists 25 | if (await dynamoUtils.checkRuleExistsByName(process.env.RULES_TABLE, ruleSetId, ruleName)) 26 | { 27 | console.log('[ERROR] rule already exists with this name: ' + ruleName); 28 | 29 | return requestUtils.buildFailureResponse(CONFLICT_HTTP_ERROR_CODE, { 30 | message: 'Rule already exists' 31 | }); 32 | } 33 | // This is a novel rule so create it 34 | else 35 | { 36 | var ruleData = await dynamoUtils.getRule(process.env.RULES_TABLE,ruleSetId,ruleId) 37 | 38 | var ruleIdNew = await dynamoUtils.insertRule(process.env.RULES_TABLE, 39 | ruleSetId, ruleName, ruleData.enabled, ruleData.description, ruleData.priority, ruleData.activation, 40 | ruleData.type, ruleData.params, ruleData.weights); 41 | 42 | // Mark the last change to now 43 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 44 | 45 | return requestUtils.buildSuccessfulResponse({ 46 | ruleId: ruleIdNew 47 | }); 48 | } 49 | } 50 | catch (error) 51 | { 52 | console.log('[ERROR] failed to create rule', error); 53 | return requestUtils.buildErrorResponse(error); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /lambda/CloneRuleSet.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var rulesEngine = require('./utils/RulesEngine.js'); 7 | var configUtils = require('./utils/ConfigUtils.js'); 8 | 9 | /** 10 | * Clones a rule set with a new name, dependencies still point to the orginal ruleset 11 | */ 12 | exports.handler = async(event, context) => 13 | { 14 | try 15 | { 16 | requestUtils.logRequest(event); 17 | requestUtils.checkOrigin(event); 18 | var user = await requestUtils.verifyAPIKey(event); 19 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 20 | 21 | var body = JSON.parse(event.body); 22 | 23 | var ruleSetId = body.ruleSetId; 24 | var newRuleSetName = body.ruleSetName; 25 | 26 | // Load all rule sets and rules 27 | var ruleSets = await dynamoUtils.getRuleSetsAndRules(process.env.RULE_SETS_TABLE, process.env.RULES_TABLE); 28 | 29 | // Find this rule set and fail if not found 30 | var ruleSet = ruleSets.find(rs => rs.ruleSetId === ruleSetId); 31 | 32 | if (ruleSet === undefined) 33 | { 34 | throw new Error('Failed to locate rule set for id: ' + ruleSetId); 35 | } 36 | 37 | // If a ruleset exists with this name, error with a nice message 38 | var ruleSetSharedName = ruleSets.find(rs => rs.name === newRuleSetName); 39 | 40 | if (ruleSetSharedName !== undefined) 41 | { 42 | return requestUtils.buildFailureResponse(409, { 43 | message: 'A rule set already exists with name: ' + newRuleSetName 44 | }); 45 | } 46 | 47 | // Clone the rule set with a new name 48 | var cloneRuleSetId = await dynamoUtils.cloneRuleSet(process.env.RULE_SETS_TABLE, process.env.RULES_TABLE, newRuleSetName, ruleSet); 49 | 50 | // Mark the last change to now 51 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 52 | 53 | // Success! 54 | return requestUtils.buildSuccessfulResponse({ 55 | message: 'Rule set cloned successfully', 56 | ruleSetId: cloneRuleSetId 57 | }); 58 | } 59 | catch (error) 60 | { 61 | console.log('[ERROR] failed to clone rule set', error); 62 | return requestUtils.buildErrorResponse(error); 63 | } 64 | }; 65 | 66 | -------------------------------------------------------------------------------- /lambda/ConnectCheckTimeout.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | var moment = require('moment'); 9 | 10 | /** 11 | * Checks for time out in an integration lamnda call 12 | * returning the customer state and potentially updating the 13 | * integration result to TIMEOUT 14 | */ 15 | exports.handler = async(event, context) => 16 | { 17 | try 18 | { 19 | var contactId = event.Details.ContactData.InitialContactId; 20 | 21 | var customerState = await dynamoUtils.getParsedCustomerState(process.env.STATE_TABLE, contactId); 22 | 23 | // Fetch all config items and load them into the top level of the customer state 24 | await configUtils.checkLastChange(process.env.CONFIG_TABLE); 25 | var configItems = await configUtils.getConfigItems(process.env.CONFIG_TABLE); 26 | 27 | var configKeys = Object.keys(configItems); 28 | 29 | configKeys.forEach(key => { 30 | customerState[key] = configItems[key]; 31 | }); 32 | 33 | requestUtils.requireParameter('IntegrationStart', customerState.IntegrationStart); 34 | requestUtils.requireParameter('CurrentRule_functionTimeout', customerState.CurrentRule_functionTimeout); 35 | 36 | var timeout = moment(customerState.IntegrationStart).add(+customerState.CurrentRule_functionTimeout, 'seconds'); 37 | 38 | var now = moment(); 39 | 40 | if (now.isAfter(timeout)) 41 | { 42 | var timeoutSeconds = now.diff(timeout, 'seconds'); 43 | console.log(`[ERROR] integration timeout by ${timeoutSeconds} detected`); 44 | customerState.IntegrationStatus = 'TIMEOUT'; 45 | customerState.IntegrationErrorCause = 'The request timed out'; 46 | customerState.IntegrationEnd = moment(); 47 | await dynamoUtils.persistCustomerState(process.env.STATE_TABLE, contactId, customerState, [ 'IntegrationStatus', 'IntegrationEnd', 'IntegrationErrorCause' ]); 48 | return requestUtils.buildCustomerStateResponse(customerState); 49 | } 50 | 51 | // Now wait for up to 2 seconds for a result 52 | var endTime = moment(now).add(2, 'seconds'); 53 | 54 | while ((customerState.IntegrationStatus === 'START' || customerState.IntegrationStatus === 'RUN') && moment().isBefore(endTime)) 55 | { 56 | customerState = await dynamoUtils.getParsedCustomerState(process.env.STATE_TABLE, contactId); 57 | } 58 | 59 | console.log(`[TIMING] function: ${customerState.CurrentRule_functionName} got status: ${customerState.IntegrationStatus} in: ${moment().diff(moment(customerState.IntegrationStart))} millis`); 60 | 61 | return requestUtils.buildCustomerStateResponse(customerState); 62 | } 63 | catch (error) 64 | { 65 | console.log('[ERROR] failed to check for integration timeout', error); 66 | throw error; 67 | } 68 | }; 69 | 70 | -------------------------------------------------------------------------------- /lambda/ConnectCreateCallHistory.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const requestUtils = require('./utils/RequestUtils'); 5 | const dynamoUtils = require('./utils/DynamoUtils'); 6 | const commonUtils = require('./utils/CommonUtils'); 7 | const moment = require('moment'); 8 | 9 | /** 10 | * Creates call history record in DynamoDB for the requested action 11 | */ 12 | exports.handler = async(event, context) => 13 | { 14 | try 15 | { 16 | requestUtils.logRequest(event); 17 | 18 | var phoneNumber = event.Details.Parameters.phoneNumber; 19 | var action = event.Details.Parameters.action; 20 | 21 | if (phoneNumber === undefined) 22 | { 23 | throw new Error('Missing required parameter: phoneNumber'); 24 | } 25 | 26 | if (action === undefined) 27 | { 28 | throw new Error('Missing required parameter: action'); 29 | } 30 | 31 | // Use Fractional seconds to ensure unique records 32 | // TODO perhaps requires a conditional insert here 33 | await dynamoUtils.insertCallHistory(process.env.CALL_HISTORY_TABLE, 34 | phoneNumber, commonUtils.nowUTCMillis(), action); 35 | 36 | return { 37 | result: 'Success' 38 | }; 39 | } 40 | catch (error) 41 | { 42 | console.log('[ERROR] failed to insert call history', error); 43 | throw error; 44 | } 45 | }; 46 | 47 | -------------------------------------------------------------------------------- /lambda/ConnectCreateCallback.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | 7 | /** 8 | * Creates a callback in DynamoDB 9 | */ 10 | exports.handler = async (event, context) => { 11 | try { 12 | requestUtils.logRequest(event); 13 | 14 | var contactId = event.Details.ContactData.InitialContactId; 15 | 16 | // Load customer state 17 | var customerState = await dynamoUtils.getParsedCustomerState(process.env.STATE_TABLE, contactId); 18 | 19 | requestUtils.requireParameter('phoneNumber', event.Details.Parameters.phoneNumber); 20 | requestUtils.requireParameter('queueArn', customerState.CurrentRule_queueArn); 21 | 22 | var phoneNumber = event.Details.Parameters.phoneNumber; 23 | var queueArn = customerState.CurrentRule_queueArn; 24 | 25 | //check if callback exists 26 | var phoneNumberInAnyCallbackQueue = await dynamoUtils.phoneNumberInAnyCallbackQueue(process.env.CALLBACK_TABLE, phoneNumber); 27 | 28 | if (phoneNumberInAnyCallbackQueue) { 29 | customerState.CurrentRule_CreateCallbackStatus = 'EXISTING'; 30 | await dynamoUtils.persistCustomerState(process.env.STATE_TABLE, contactId, customerState, ['CurrentRule_CreateCallbackStatus']); 31 | return requestUtils.buildCustomerStateResponse(customerState); 32 | } else { 33 | await dynamoUtils.insertCallback(process.env.CALLBACK_TABLE, phoneNumber, queueArn); 34 | customerState.CurrentRule_CreateCallbackStatus = 'CREATED'; 35 | customerState.AlternateCallbackPhoneNumber = phoneNumber; 36 | await dynamoUtils.persistCustomerState(process.env.STATE_TABLE, contactId, customerState, ['CurrentRule_CreateCallbackStatus', 'AlternateCallbackPhoneNumber']); 37 | return requestUtils.buildCustomerStateResponse(customerState); 38 | } 39 | } catch (error) { 40 | console.log('[ERROR] Failed to create callback', error); 41 | throw error; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /lambda/ConnectDeleteCallback.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | 7 | /** 8 | * Deletes a callback from DynamoDB 9 | */ 10 | exports.handler = async (event, context) => { 11 | try { 12 | var contactId = event.Details.ContactData.InitialContactId; 13 | 14 | requestUtils.requireParameter('ContactId', contactId); 15 | 16 | //load customer state 17 | var customerState = await dynamoUtils.getParsedCustomerState(process.env.STATE_TABLE, contactId); 18 | 19 | if (customerState.AlternateCallbackPhoneNumber) { 20 | var phoneNumber = customerState.AlternateCallbackPhoneNumber; 21 | } else { 22 | var phoneNumber = customerState.OriginalCustomerNumber; 23 | } 24 | 25 | var queueArn = customerState.CurrentRule_queueArn; 26 | 27 | if (phoneNumber === undefined) { 28 | throw new Error('Missing required parameter: phoneNumber'); 29 | } 30 | 31 | if (queueArn === undefined) { 32 | throw new Error('Missing required parameter: queueArn'); 33 | } 34 | 35 | console.log(`[INFO] Deleting callback for ${phoneNumber} on queue ${queueArn}`); 36 | 37 | await dynamoUtils.deleteCallback(process.env.CALLBACK_TABLE, phoneNumber, queueArn); 38 | 39 | return requestUtils.buildCustomerStateResponse(customerState); 40 | } catch (error) { 41 | console.log('[ERROR] Failed to delete callback', error); 42 | throw error; 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /lambda/ConnectLoadState.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Loads state for this contact 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | var contactId = event.Details.ContactData.InitialContactId; 16 | 17 | var customerState = await dynamoUtils.getParsedCustomerState(process.env.STATE_TABLE, contactId); 18 | 19 | // Fetch all config items and load them into the top level of the customer state 20 | await configUtils.checkLastChange(process.env.CONFIG_TABLE); 21 | var configItems = await configUtils.getConfigItems(process.env.CONFIG_TABLE); 22 | 23 | var configKeys = Object.keys(configItems); 24 | 25 | configKeys.forEach(key => { 26 | customerState[key] = configItems[key]; 27 | }); 28 | 29 | return requestUtils.buildCustomerStateResponse(customerState); 30 | } 31 | catch (error) 32 | { 33 | console.log('[ERROR] failed to load state', error); 34 | throw error; 35 | } 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /lambda/ConnectSendSMS.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const requestUtils = require('./utils/RequestUtils'); 5 | const dynamoUtils = require('./utils/DynamoUtils'); 6 | const snsUtils = require('./utils/SNSUtils'); 7 | const pinpointUtils = require('./utils/PinpointUtils'); 8 | const inferenceUtils = require('./utils/InferenceUtils'); 9 | const commonUtils = require('./utils/CommonUtils'); 10 | 11 | /** 12 | * Handles sending an SMS via SNS 13 | */ 14 | exports.handler = async(event, context) => 15 | { 16 | try 17 | { 18 | requestUtils.logRequest(event); 19 | 20 | // Load customer state 21 | var contactId = event.Details.ContactData.InitialContactId; 22 | var customerState = await dynamoUtils.getParsedCustomerState(process.env.STATE_TABLE, contactId); 23 | 24 | requestUtils.requireParameter('CurrentRule_phoneNumberKey', customerState.CurrentRule_phoneNumberKey); 25 | requestUtils.requireParameter('CurrentRule_message', customerState.CurrentRule_message); 26 | 27 | var phoneNumber = inferenceUtils.getStateValue(customerState, customerState.CurrentRule_phoneNumberKey); 28 | var message = customerState.CurrentRule_message; 29 | 30 | var stateToSave = new Set(); 31 | 32 | var pinpointApplicationId = process.env.PINPOINT_APPLICATION_ID; 33 | var originationNumber = process.env.ORIGINATION_NUMBER; 34 | 35 | if (commonUtils.isEmptyString(phoneNumber)) 36 | { 37 | console.error(`${contactId} no phone number provided, cannot send SMS`); 38 | inferenceUtils.updateState(customerState, stateToSave, 'System.LastSMSStatus', 'ERROR'); 39 | } 40 | else 41 | { 42 | console.info(`${contactId} sending SMS to: ${phoneNumber}`); 43 | 44 | try 45 | { 46 | if (!commonUtils.isEmptyString(pinpointApplicationId) && 47 | !commonUtils.isEmptyString(originationNumber)) 48 | { 49 | console.info(`${contactId} sending SMS via Pinpoint`) 50 | await pinpointUtils.sendSMS(phoneNumber, message, originationNumber, pinpointApplicationId, 'TRANSACTIONAL'); 51 | inferenceUtils.updateState(customerState, stateToSave, 'System.LastSMSStatus', 'SENT'); 52 | console.info(`${contactId} SMS queued successfully via Pinpoint`); 53 | } 54 | else 55 | { 56 | console.info(`${contactId} sending SMS via SNS`); 57 | await snsUtils.sendSMS(phoneNumber, message); 58 | inferenceUtils.updateState(customerState, stateToSave, 'System.LastSMSStatus', 'SENT'); 59 | console.info(`${contactId} SMS queued successfully via SNS`); 60 | } 61 | } 62 | catch (smsError) 63 | { 64 | console.error(`${contactId} failed to send SMS to: ${phoneNumber}`, smsError); 65 | inferenceUtils.updateState(customerState, stateToSave, 'System.LastSMSStatus', 'ERROR'); 66 | } 67 | } 68 | 69 | await dynamoUtils.persistCustomerState(process.env.STATE_TABLE, contactId, customerState, Array.from(stateToSave)); 70 | 71 | return { 72 | Success: 'true' 73 | }; 74 | } 75 | catch (error) 76 | { 77 | console.error('Failed to send SMS', error); 78 | throw error; 79 | } 80 | }; 81 | 82 | -------------------------------------------------------------------------------- /lambda/ConnectUpdateState.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const requestUtils = require('./utils/RequestUtils'); 5 | const dynamoUtils = require('./utils/DynamoUtils'); 6 | const configUtils = require('./utils/ConfigUtils'); 7 | const inferenceUtils = require('./utils/InferenceUtils'); 8 | const commonUtils = require('./utils/CommonUtils'); 9 | const moment = require('moment'); 10 | 11 | /** 12 | * Sets a range of state flags for a customer expecting input parameters in the format: 13 | * key1 = 'stateKey' 14 | * value1 = 'stateValue' 15 | * 16 | * If the value is 'increment' this will add one to an existing value 17 | * and if the value is undefined will set it to 1 18 | * 19 | * Missing or empty values for keys will result in state deletions for that key. 20 | * 21 | * Gaps in key indices are not currently supported. 22 | * 23 | * Loads and returns all state values for this contact in the response in the format: 24 | * 25 | * { 26 | * stateKey: stateValue, 27 | * ... 28 | * } 29 | * 30 | */ 31 | exports.handler = async(event, context) => 32 | { 33 | try 34 | { 35 | var contactId = event.Details.ContactData.InitialContactId; 36 | 37 | var statesToAdd = []; 38 | var statesToRemove = []; 39 | 40 | var index = 1; 41 | 42 | // Load customer state 43 | var customerState = await dynamoUtils.getParsedCustomerState(process.env.STATE_TABLE, contactId); 44 | 45 | // Fetch all config items and load them into the top level of the customer state 46 | await configUtils.checkLastChange(process.env.CONFIG_TABLE); 47 | var configItems = await configUtils.getConfigItems(process.env.CONFIG_TABLE); 48 | 49 | var configKeys = Object.keys(configItems); 50 | 51 | configKeys.forEach(configKey => { 52 | customerState[configKey] = configItems[configKey]; 53 | }); 54 | 55 | var stateToSave = new Set(); 56 | 57 | while (event.Details.Parameters['key' + index] !== undefined) 58 | { 59 | var key = event.Details.Parameters['key' + index]; 60 | 61 | if (key === '') 62 | { 63 | continue; 64 | } 65 | 66 | var value = event.Details.Parameters['value' + index]; 67 | 68 | if (value === 'increment') 69 | { 70 | inferenceUtils.incrementStateValue(customerState, stateToSave, key); 71 | } 72 | else 73 | { 74 | inferenceUtils.updateState(customerState, stateToSave, key, value); 75 | } 76 | 77 | index++; 78 | } 79 | 80 | console.log('[INFO] found states to update: ' + Array.from(stateToSave).join(', ')); 81 | 82 | // Persist the changed state fields to DynamoDB 83 | await dynamoUtils.persistCustomerState(process.env.STATE_TABLE, contactId, customerState, Array.from(stateToSave)); 84 | 85 | // Echo back the customer state 86 | return requestUtils.buildCustomerStateResponse(customerState); 87 | } 88 | catch (error) 89 | { 90 | console.log('[ERROR] failed to update customer state', error); 91 | throw error; 92 | } 93 | }; 94 | 95 | -------------------------------------------------------------------------------- /lambda/ContactEventListener.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var connectUtils = require('./utils/ConnectUtils.js'); 7 | 8 | var moment = require('moment-timezone'); 9 | 10 | /** 11 | * Listens for contact events from Amazon Connect 12 | */ 13 | exports.handler = async(event, context) => 14 | { 15 | console.info(JSON.stringify(event, null, 2)); 16 | 17 | if (event.detail.eventType === 'DISCONNECTED') 18 | { 19 | await loadContactAttributesAndLog(event); 20 | } 21 | } 22 | 23 | /** 24 | * Loads contact attributes and logs the event 25 | */ 26 | async function loadContactAttributesAndLog(event) 27 | { 28 | try 29 | { 30 | var initialContactId = event.detail.contactId; 31 | 32 | if (event.detail.initialContactId !== undefined) 33 | { 34 | initialContactId = event.detail.initialContactId; 35 | } 36 | 37 | var customerState = await dynamoUtils.getParsedCustomerState(process.env.STATE_TABLE, initialContactId); 38 | 39 | var existingAttributes = await connectUtils.getContactAttributes(process.env.INSTANCE_ID, initialContactId); 40 | var attributesDelta = connectUtils.computeAttributesDelta(existingAttributes, customerState.ContactAttributes); 41 | 42 | // Just concern ourselves with state attributes that have changed wrt to connect attributes 43 | if (attributesDelta.length > 0) 44 | { 45 | attributesDelta.forEach(delta => { 46 | existingAttributes[delta.key] = delta.value; 47 | }); 48 | console.info(`ContactId: ${initialContactId} contact attributes have changed writing final attributes: ${JSON.stringify(existingAttributes, null, 2)}`); 49 | await connectUtils.updateContactAttributes(process.env.INSTANCE_ID, initialContactId, existingAttributes); 50 | } 51 | else 52 | { 53 | console.info(`ContactId: ${initialContactId} contact attributes have not changed`); 54 | } 55 | 56 | var sortedAttributes = {}; 57 | 58 | var keys = Object.keys(existingAttributes).sort(); 59 | keys.forEach(key => { 60 | sortedAttributes[key] = existingAttributes[key]; 61 | }); 62 | 63 | var logPayload = { 64 | EventType: 'ANALYTICS', 65 | EventCode: 'CONTACT_EVENT', 66 | ContactEventType: event.detail.eventType, 67 | ContactId: initialContactId, 68 | RuleSet: customerState.CurrentRuleSet, 69 | RuleType: customerState.CurrentRuleType, 70 | RuleName: customerState.CurrentRule, 71 | When: moment().format('YYYY-MM-DDTHH:mm:ss.SSSZ'), 72 | ConnectEvent: event, 73 | ContactAttributes: sortedAttributes 74 | }; 75 | 76 | console.log(JSON.stringify(logPayload, null, 2)); 77 | 78 | return logPayload; 79 | } 80 | catch (error) 81 | { 82 | console.error(`Failed to load contact attributes and log event for contact id: [${initialContactId}]`, error); 83 | throw error; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lambda/CopyTests.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Copies tests to a new folder 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 19 | 20 | var body = JSON.parse(event.body); 21 | var testIds = body.testIds; 22 | var copyFolder = body.copyFolder; 23 | 24 | await dynamoUtils.copyTests(process.env.TESTS_TABLE, testIds, copyFolder); 25 | 26 | // Mark the last change to now 27 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 28 | 29 | return requestUtils.buildSuccessfulResponse({ 30 | message: 'Tests copied successfully' 31 | }); 32 | } 33 | catch (error) 34 | { 35 | console.log('[ERROR] failed to copy tests', error); 36 | return requestUtils.buildErrorResponse(error); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /lambda/CreateEndPoint.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Creates a new end point in DynamoDB 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 19 | 20 | var body = JSON.parse(event.body); 21 | var name = body.name; 22 | var inboundNumbers = body.inboundNumbers; 23 | var description = body.description; 24 | var enabled = body.enabled; 25 | 26 | var endPoints = await dynamoUtils.getEndPoints(process.env.END_POINTS_TABLE); 27 | 28 | // Check for an existing end point with this name 29 | var existingEndPoint = endPoints.find(endPoint => endPoint.name.toLowerCase() === name.toLowerCase()); 30 | 31 | if (existingEndPoint !== undefined) 32 | { 33 | console.log('[ERROR] end point already exists with this name: ' + name); 34 | 35 | return requestUtils.buildFailureResponse(409, { 36 | message: 'End point already exists for name' 37 | }); 38 | } 39 | 40 | // This is a novel end point so create it 41 | var endPointId = await dynamoUtils.insertEndPoint(process.env.END_POINTS_TABLE, 42 | name, description, inboundNumbers, enabled); 43 | 44 | // Mark the last change to now 45 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 46 | 47 | return requestUtils.buildSuccessfulResponse({ 48 | endPointId: endPointId 49 | }); 50 | } 51 | catch (error) 52 | { 53 | console.log('[ERROR] failed to create end point', error); 54 | return requestUtils.buildErrorResponse(error); 55 | } 56 | }; 57 | 58 | -------------------------------------------------------------------------------- /lambda/CreateHoliday.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | const { v4: uuidv4 } = require('uuid'); 9 | 10 | /** 11 | * Creates a new holiday in DynamoDB 12 | */ 13 | exports.handler = async(event, context) => 14 | { 15 | try 16 | { 17 | requestUtils.logRequest(event); 18 | requestUtils.checkOrigin(event); 19 | var user = await requestUtils.verifyAPIKey(event); 20 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 21 | 22 | var body = JSON.parse(event.body); 23 | var when = body.when; 24 | var name = body.name; 25 | var description = body.description; 26 | var closed = body.closed; 27 | 28 | await configUtils.checkLastChange(process.env.CONFIG_TABLE); 29 | 30 | var holidays = await configUtils.getHolidays(process.env.CONFIG_TABLE); 31 | 32 | var holidayId = uuidv4(); 33 | 34 | holidays.push({ 35 | holidayId: holidayId, 36 | when: when, 37 | name: name, 38 | description: description, 39 | closed: closed 40 | }); 41 | 42 | var holidaysToSave = JSON.stringify(holidays); 43 | 44 | await configUtils.updateConfigItem(process.env.CONFIG_TABLE, 'Holidays', holidaysToSave); 45 | 46 | // Mark the last change to now 47 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 48 | 49 | return requestUtils.buildSuccessfulResponse({ 50 | holidayId: holidayId 51 | }); 52 | } 53 | catch (error) 54 | { 55 | console.log('[ERROR] failed to create holiday', error); 56 | return requestUtils.buildErrorResponse(error); 57 | } 58 | }; 59 | 60 | -------------------------------------------------------------------------------- /lambda/CreateRule.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | var {validateRuleParams} = require("./utils/HandlebarsUtils"); 8 | 9 | /** 10 | * Creates a new rule in DynamoDB 11 | */ 12 | exports.handler = async(event, context) => 13 | { 14 | try 15 | { 16 | requestUtils.logRequest(event); 17 | requestUtils.checkOrigin(event); 18 | var user = await requestUtils.verifyAPIKey(event); 19 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 20 | 21 | var body = JSON.parse(event.body); 22 | var ruleSetId = body.ruleSetId; 23 | var ruleName = body.ruleName; 24 | var ruleEnabled = body.ruleEnabled; 25 | var ruleDescription = body.ruleDescription; 26 | var rulePriority = body.rulePriority; 27 | var ruleActivation = body.ruleActivation; 28 | var ruleType = body.ruleType; 29 | var params = body.params; 30 | var weights = body.weights; 31 | 32 | const {valid, lastFailedError} = validateRuleParams(params) 33 | 34 | if (!valid) 35 | { 36 | lastFailedError.statusCode = 400 // Bad request 37 | return requestUtils.buildErrorResponse(lastFailedError); 38 | } 39 | 40 | // Check for an existing rule with this name in this rule set and fail if it exists 41 | if (await dynamoUtils.checkRuleExistsByName(process.env.RULES_TABLE, ruleSetId, ruleName)) 42 | { 43 | console.log('[ERROR] rule already exists with this name: ' + ruleName); 44 | 45 | return requestUtils.buildFailureResponse(409, { 46 | message: 'Rule already exists' 47 | }); 48 | } 49 | // This is a novel rule so create it 50 | else 51 | { 52 | var ruleId = await dynamoUtils.insertRule(process.env.RULES_TABLE, 53 | ruleSetId, ruleName, ruleEnabled, ruleDescription, rulePriority, ruleActivation, 54 | ruleType, params, weights); 55 | 56 | // Mark the last change to now 57 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 58 | 59 | return requestUtils.buildSuccessfulResponse({ 60 | ruleId: ruleId 61 | }); 62 | } 63 | } 64 | catch (error) 65 | { 66 | console.log('[ERROR] failed to create rule', error); 67 | return requestUtils.buildErrorResponse(error); 68 | } 69 | }; 70 | 71 | -------------------------------------------------------------------------------- /lambda/CreateRuleSet.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Creates a new rule set in DynamoDB 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 19 | 20 | var body = JSON.parse(event.body); 21 | var ruleSetName = body.ruleSetName; 22 | var ruleSetEnabled = body.ruleSetEnabled; 23 | var ruleSetDescription = body.ruleSetDescription; 24 | var endPoints = body.endPoints; 25 | var folder = body.folder; 26 | 27 | // Check for an existing rule set with this name and fail if it exists 28 | if (await dynamoUtils.checkRuleSetExistsByName(process.env.RULE_SETS_TABLE, ruleSetName)) 29 | { 30 | console.log('[ERROR] rule set already exists with this name: ' + ruleSetName); 31 | 32 | return requestUtils.buildFailureResponse(409, { 33 | message: 'Rule set already exists' 34 | }); 35 | } 36 | // This is a novel rule set so create it 37 | else 38 | { 39 | var ruleSetId = await dynamoUtils.insertRuleSet(process.env.RULE_SETS_TABLE, 40 | ruleSetName, ruleSetEnabled, ruleSetDescription, endPoints, folder); 41 | 42 | // Mark the last change to now 43 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 44 | 45 | return requestUtils.buildSuccessfulResponse({ 46 | ruleSetId: ruleSetId 47 | }); 48 | } 49 | } 50 | catch (error) 51 | { 52 | console.log('[ERROR] failed to create rule set', error); 53 | return requestUtils.buildErrorResponse(error); 54 | } 55 | }; 56 | 57 | -------------------------------------------------------------------------------- /lambda/CreateTest.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Creates a new test in DynamoDB, tests don't need to have a unique name 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 19 | 20 | var body = JSON.parse(event.body); 21 | var name = body.name; 22 | var productionReady = body.productionReady; 23 | var folder = body.folder; 24 | var testReference = body.testReference; 25 | var description = body.description; 26 | var endPoint = body.endPoint; 27 | var testDateTime = body.testDateTime; 28 | var customerPhoneNumber = body.customerPhoneNumber; 29 | var payload = body.payload; 30 | var contactAttributes = body.contactAttributes; 31 | 32 | var testId = await dynamoUtils.insertTest(process.env.TESTS_TABLE, 33 | name, productionReady, folder, testReference, description, endPoint, 34 | testDateTime, customerPhoneNumber, payload, contactAttributes); 35 | 36 | // Mark the last change to now 37 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 38 | 39 | return requestUtils.buildSuccessfulResponse({ 40 | testId: testId 41 | }); 42 | 43 | } 44 | catch (error) 45 | { 46 | console.log('[ERROR] failed to create test', error); 47 | return requestUtils.buildErrorResponse(error); 48 | } 49 | }; 50 | 51 | -------------------------------------------------------------------------------- /lambda/CreateUser.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Creates a new user in DynamoDB 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR']); 19 | 20 | var body = JSON.parse(event.body); 21 | var firstName = body.firstName; 22 | var lastName = body.lastName; 23 | var emailAddress = body.emailAddress; 24 | var userRole = body.userRole; 25 | var apiKey = body.apiKey; 26 | var userEnabled = body.userEnabled; 27 | 28 | // Check for an existing user with this email 29 | var existingUser = await dynamoUtils.getUserByEmailAddress(process.env.USERS_TABLE, emailAddress); 30 | if (existingUser !== undefined) 31 | { 32 | console.log('[ERROR] user already exists with this email address: ' + emailAddress); 33 | 34 | return requestUtils.buildFailureResponse(409, { 35 | message: 'User already exists for email' 36 | }); 37 | } 38 | 39 | // Check for an existing user with this API key 40 | existingUser = await dynamoUtils.getUserByAPIKey(process.env.USERS_TABLE, apiKey); 41 | if (existingUser !== undefined) 42 | { 43 | console.log('[ERROR] user already exists with this API key: ' + apiKey); 44 | 45 | return requestUtils.buildFailureResponse(409, { 46 | message: 'User already exists with API key' 47 | }); 48 | } 49 | 50 | // This is a novel user so create it 51 | var userId = await dynamoUtils.insertUser(process.env.USERS_TABLE, 52 | firstName, lastName, emailAddress, userRole, apiKey, userEnabled); 53 | 54 | // Mark the last change to now 55 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 56 | 57 | return requestUtils.buildSuccessfulResponse({ 58 | userId: userId 59 | }); 60 | } 61 | catch (error) 62 | { 63 | console.log('[ERROR] failed to create user', error); 64 | return requestUtils.buildErrorResponse(error); 65 | } 66 | }; 67 | 68 | -------------------------------------------------------------------------------- /lambda/CreateWeight.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | const { v4: uuidv4 } = require('uuid'); 9 | 10 | /** 11 | * Creates a new weight for a rule in DynamoDB 12 | */ 13 | exports.handler = async(event, context) => 14 | { 15 | try 16 | { 17 | requestUtils.logRequest(event); 18 | requestUtils.checkOrigin(event); 19 | var user = await requestUtils.verifyAPIKey(event); 20 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 21 | 22 | var body = JSON.parse(event.body); 23 | var ruleSetId = body.ruleSetId; 24 | var ruleId = body.ruleId; 25 | var field = body.field; 26 | var operation = body.operation; 27 | var value = body.value; 28 | var weight = body.weight; 29 | 30 | var newWeight = { 31 | weightId: uuidv4(), 32 | field: field, 33 | operation: operation, 34 | value: value, 35 | weight: weight 36 | }; 37 | 38 | await dynamoUtils.insertWeight(process.env.RULES_TABLE, ruleSetId, ruleId, newWeight); 39 | 40 | // Mark the last change to now 41 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 42 | 43 | return requestUtils.buildSuccessfulResponse({ 44 | message: 'Weight created successfully' 45 | }); 46 | } 47 | catch (error) 48 | { 49 | console.log('[ERROR] failed to create weight', error); 50 | return requestUtils.buildErrorResponse(error); 51 | } 52 | }; 53 | 54 | -------------------------------------------------------------------------------- /lambda/DescribeLexBot.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var moment = require('moment-timezone'); 5 | 6 | var requestUtils = require('./utils/RequestUtils.js'); 7 | var lexUtils = require('./utils/LexUtils.js'); 8 | 9 | /** 10 | * Describes a lex bot 11 | */ 12 | exports.handler = async(event, context) => 13 | { 14 | try 15 | { 16 | requestUtils.logRequest(event); 17 | requestUtils.checkOrigin(event); 18 | var user = await requestUtils.verifyAPIKey(event); 19 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 20 | 21 | var body = JSON.parse(event.body); 22 | var botName = body.botName; 23 | 24 | var botAlias = process.env.BOT_ALIAS; 25 | var botLocalId = process.env.BOT_LOCALE_ID; 26 | 27 | console.log('[INFO] got input: ' + JSON.stringify(body, null, 2)); 28 | 29 | var qualifiedBotName = `${process.env.STAGE}-${process.env.SERVICE}-${botName}`; 30 | 31 | // Load the bot by name to get the bot id 32 | var bot = await lexUtils.getBotByName(qualifiedBotName); 33 | 34 | console.log('[INFO] got bot: ' + JSON.stringify(bot, null, 2)); 35 | 36 | // Load up the PROD alias to get the version 37 | var botAliases = await lexUtils.listBotAliases(bot.botId); 38 | 39 | console.log('[INFO] got aliases: ' + JSON.stringify(botAliases, null, 2)); 40 | 41 | // Find the prod alias 42 | var prodAlias = botAliases.find(a => a.botAliasName === botAlias); 43 | 44 | // Look for the prod alias 45 | if (prodAlias === undefined) 46 | { 47 | throw new Error('Failed to locate alias: ' + alias); 48 | } 49 | 50 | console.log('[INFO] got PROD alias: ' + JSON.stringify(prodAlias, null, 2)); 51 | 52 | // List the intents for the aliased version and locale 53 | var intents = await lexUtils.listIntents(bot.botId, prodAlias.botVersion, botLocalId); 54 | 55 | var response = { 56 | botName: botName, 57 | fullBotName: qualifiedBotName, 58 | botId: bot.botId, 59 | alias: prodAlias.botAliasName, 60 | botAliasId: prodAlias.botAliasId, 61 | intents: intents 62 | }; 63 | 64 | console.log('[INFO] described lex bot: ' + JSON.stringify(response, null, 2)); 65 | 66 | return requestUtils.buildSuccessfulResponse({ 67 | lexBot: response 68 | }); 69 | } 70 | catch (error) 71 | { 72 | console.log('[ERROR] failed to describe lex bot', error); 73 | return requestUtils.buildErrorResponse(error); 74 | } 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /lambda/GetBatches.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | 7 | /** 8 | * Fetches batches one by one or fetches a list 9 | */ 10 | exports.handler = async(event, context) => 11 | { 12 | try 13 | { 14 | requestUtils.logRequest(event); 15 | requestUtils.checkOrigin(event); 16 | var user = await requestUtils.verifyAPIKey(event); 17 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 18 | 19 | var batchId = undefined; 20 | var mineOnly = false; 21 | var errorsOnly = false; 22 | var limit = 50; 23 | 24 | // Do some filtering of the results 25 | if (event.queryStringParameters !== null) 26 | { 27 | batchId = event.queryStringParameters.batchId; 28 | mineOnly = event.queryStringParameters.mineOnly === 'true'; 29 | errorsOnly = event.queryStringParameters.errorsOnly === 'true'; 30 | } 31 | 32 | console.info(`Got mineOnly: ${mineOnly} errorsOnly: ${errorsOnly} batchId: ${batchId} limit: ${limit}`); 33 | 34 | var batches = []; 35 | 36 | if (batchId !== undefined) 37 | { 38 | var batch = await dynamoUtils.getBatch(process.env.VERIFY_TABLE, batchId); 39 | batches = [ batch ]; 40 | } 41 | else 42 | { 43 | var rawBatches = await dynamoUtils.getBatches(process.env.VERIFY_TABLE); 44 | 45 | // Filter results on errors / warnings only or filter by just the current user 46 | if (mineOnly || errorsOnly) 47 | { 48 | rawBatches.forEach(batch => { 49 | 50 | var accepted = true; 51 | 52 | if (mineOnly && (batch.userId !== user.userId)) 53 | { 54 | accepted = false; 55 | } 56 | 57 | if (errorsOnly && (batch.success === true && batch.warning === false)) 58 | { 59 | accepted = false; 60 | } 61 | 62 | if (accepted) 63 | { 64 | batches.push(batch); 65 | } 66 | }); 67 | } 68 | else 69 | { 70 | batches = rawBatches; 71 | } 72 | } 73 | 74 | // Sort by descending start time then clamp to limit 75 | batches.sort(function(a, b) 76 | { 77 | return b.startTime.localeCompare(a.startTime); 78 | }); 79 | 80 | if (batches.length > limit) 81 | { 82 | console.info('Limiting results to: ' + limit); 83 | batches = batches.slice(0, limit); 84 | } 85 | 86 | return requestUtils.buildSuccessfulResponse({ 87 | batches: batches 88 | }); 89 | } 90 | catch (error) 91 | { 92 | console.log('[ERROR] failed to load batches', error); 93 | return requestUtils.buildErrorResponse(error); 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /lambda/GetConnectData.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var moment = require('moment-timezone'); 5 | 6 | var requestUtils = require('./utils/RequestUtils.js'); 7 | var configUtils = require('./utils/ConfigUtils.js'); 8 | 9 | /** 10 | * Fetches connect configuration 11 | */ 12 | exports.handler = async(event, context) => 13 | { 14 | try 15 | { 16 | requestUtils.logRequest(event); 17 | requestUtils.checkOrigin(event); 18 | var user = await requestUtils.verifyAPIKey(event); 19 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 20 | 21 | // Check for config changes 22 | await configUtils.checkLastChange(process.env.CONFIG_TABLE); 23 | 24 | var contactFlows = await configUtils.getContactFlows(process.env.CONFIG_TABLE); 25 | var queues = await configUtils.getQueues(process.env.CONFIG_TABLE); 26 | var lambdaFunctions = await configUtils.getLambdaFunctions(process.env.CONFIG_TABLE); 27 | var phoneNumbers = await configUtils.getPhoneNumbers(process.env.CONFIG_TABLE); 28 | var timeZone = await configUtils.getCallCentreTimeZone(process.env.CONFIG_TABLE); 29 | var prompts = await configUtils.getPrompts(process.env.CONFIG_TABLE); 30 | var operatingHours = await configUtils.getOperatingHours(process.env.CONFIG_TABLE); 31 | var lexBots = await configUtils.getLexBots(process.env.CONFIG_TABLE); 32 | var routingProfiles = await configUtils.getRoutingProfiles(process.env.CONFIG_TABLE); 33 | 34 | var promptNames = []; 35 | prompts.forEach(prompt => { 36 | promptNames.push(prompt.Name); 37 | }); 38 | 39 | var operatingHoursNames = []; 40 | operatingHours.forEach(operatingHour => { 41 | operatingHoursNames.push(operatingHour.Name); 42 | }); 43 | 44 | var localDateTime = moment().tz(timeZone); 45 | 46 | return requestUtils.buildSuccessfulResponse({ 47 | queues: queues, 48 | contactFlows: contactFlows, 49 | phoneNumbers: phoneNumbers, 50 | lambdaFunctions: lambdaFunctions, 51 | operatingHours: operatingHoursNames, 52 | routingProfiles: routingProfiles, 53 | timeZone: timeZone, 54 | prompts: promptNames, 55 | localDateTime: localDateTime.format(), 56 | localTime: localDateTime.format('hh:mm A'), 57 | lexBots: lexBots 58 | }); 59 | } 60 | catch (error) 61 | { 62 | console.log('[ERROR] failed to load Connect data', error); 63 | return requestUtils.buildErrorResponse(error); 64 | } 65 | }; 66 | 67 | -------------------------------------------------------------------------------- /lambda/GetEndPoints.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | 7 | /** 8 | * Fetches all end points 9 | */ 10 | exports.handler = async(event, context) => 11 | { 12 | try 13 | { 14 | requestUtils.logRequest(event); 15 | requestUtils.checkOrigin(event); 16 | var user = await requestUtils.verifyAPIKey(event); 17 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 18 | 19 | var endPoints = await dynamoUtils.getEndPoints(process.env.END_POINTS_TABLE); 20 | 21 | return requestUtils.buildSuccessfulResponse({ 22 | endPoints: endPoints 23 | }); 24 | } 25 | catch (error) 26 | { 27 | console.log('[ERROR] failed to get end points', error); 28 | return requestUtils.buildErrorResponse(error); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /lambda/GetHolidays.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Fetches the holidays in the system 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 19 | 20 | await configUtils.checkLastChange(process.env.CONFIG_TABLE); 21 | 22 | return requestUtils.buildSuccessfulResponse({ 23 | holidays: await configUtils.getHolidays(process.env.CONFIG_TABLE) 24 | }); 25 | } 26 | catch (error) 27 | { 28 | console.log('[ERROR] failed to load holidays', error); 29 | return requestUtils.buildErrorResponse(error); 30 | } 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /lambda/GetLastChange.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var configUtils = require('./utils/ConfigUtils.js'); 6 | 7 | /** 8 | * Fetches the date of the last change to the rules data model 9 | * as an ISO860 UTC timestamp 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 19 | 20 | var lastChangeTimestamp = await configUtils.getLastChangeTimestamp(process.env.CONFIG_TABLE); 21 | 22 | return requestUtils.buildSuccessfulResponse({ 23 | lastChangeTimestamp: lastChangeTimestamp 24 | }); 25 | } 26 | catch (error) 27 | { 28 | console.log('[ERROR] failed to load last change timestamp', error); 29 | return requestUtils.buildErrorResponse(error); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /lambda/GetRule.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | 7 | /** 8 | * Fetches a rule 9 | */ 10 | exports.handler = async(event, context) => 11 | { 12 | try 13 | { 14 | requestUtils.logRequest(event); 15 | requestUtils.checkOrigin(event); 16 | var user = await requestUtils.verifyAPIKey(event); 17 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 18 | 19 | var ruleSetId = event.queryStringParameters.ruleSetId; 20 | var ruleId = event.queryStringParameters.ruleId; 21 | 22 | var rule = await dynamoUtils.getRule(process.env.RULES_TABLE, ruleSetId, ruleId); 23 | 24 | return requestUtils.buildSuccessfulResponse({ 25 | rule: rule 26 | }); 27 | } 28 | catch (error) 29 | { 30 | console.log('[ERROR] failed to load rule', error); 31 | return requestUtils.buildErrorResponse(error); 32 | } 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /lambda/GetRuleSets.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | 7 | /** 8 | * Fetches a single rule set by id or all rule sets 9 | */ 10 | exports.handler = async(event, context) => 11 | { 12 | try 13 | { 14 | requestUtils.logRequest(event); 15 | requestUtils.checkOrigin(event); 16 | var user = await requestUtils.verifyAPIKey(event); 17 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 18 | 19 | var ruleSets = []; 20 | var ruleSetId = undefined; 21 | 22 | if (event.queryStringParameters !== null) 23 | { 24 | ruleSetId = event.queryStringParameters.ruleSetId; 25 | } 26 | 27 | if (ruleSetId !== undefined) 28 | { 29 | var ruleSet = await dynamoUtils.getRuleSet(process.env.RULE_SETS_TABLE, process.env.RULES_TABLE, ruleSetId); 30 | ruleSets = [ ruleSet ]; 31 | } 32 | else 33 | { 34 | ruleSets = await dynamoUtils.getAllRuleSets(process.env.RULE_SETS_TABLE); 35 | } 36 | 37 | return requestUtils.buildSuccessfulResponse({ 38 | ruleSets: ruleSets 39 | }); 40 | } 41 | catch (error) 42 | { 43 | console.log('[ERROR] failed to load rule sets', error); 44 | return requestUtils.buildErrorResponse(error); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /lambda/GetRuleSetsForCSVExport.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var papa = require('papaparse'); 5 | var requestUtils = require('./utils/RequestUtils.js'); 6 | var dynamoUtils = require('./utils/DynamoUtils.js'); 7 | 8 | /** 9 | * Fetches all rule sets and rules for export 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 19 | 20 | var ruleSets = await dynamoUtils.getRuleSetsAndRules(process.env.RULE_SETS_TABLE, process.env.RULES_TABLE); 21 | 22 | // Remove the rule set ids, rule ids and weight ids from the response 23 | ruleSets.forEach(ruleSet => { 24 | 25 | ruleSet.ruleSetId = undefined; 26 | 27 | ruleSet.rules.forEach(rule => { 28 | rule.ruleId = undefined; 29 | rule.ruleSetId = undefined; 30 | 31 | rule.weights.forEach(weight => { 32 | weight.weightId = undefined; 33 | }); 34 | }); 35 | }); 36 | 37 | var csvData = convertJSONToCSV(ruleSets) 38 | 39 | return requestUtils.buildSuccessfulResponse({ 40 | csvData 41 | }); 42 | } 43 | catch (error) 44 | { 45 | console.log('[ERROR] failed to load rule sets for export', error); 46 | return requestUtils.buildErrorResponse(error); 47 | } 48 | }; 49 | 50 | var convertJSONToCSV = (ruleSets) => { 51 | var csvData = []; 52 | 53 | ruleSets.forEach(ruleSet => { 54 | ruleSet.rules.forEach(rule => { 55 | 56 | csvData.push({ 57 | RuleSet: ruleSet.name, 58 | Rule: rule.name, 59 | RuleType: rule.type, 60 | Folder: ruleSet.folder, 61 | IsMessage: 'false', 62 | IsPrompt: 'false', 63 | IsDynamic: 'false', 64 | Name: 'ruleDescription', 65 | Value: rule.description 66 | }); 67 | 68 | var keys = Object.keys(rule.params); 69 | keys.forEach(key => 70 | { 71 | var isMessage = '' + key.toLowerCase().includes('message'); 72 | 73 | var value = ('' + rule.params[key]).trim(); 74 | value = value.replace(/(\r\n)+|\n+/g, ' '); 75 | 76 | var isPrompt = 'false'; 77 | 78 | if (isMessage && value.startsWith('prompt:')) 79 | { 80 | isPrompt = 'true'; 81 | } 82 | 83 | csvData.push({ 84 | RuleSet: ruleSet.name, 85 | Rule: rule.name, 86 | RuleType: rule.type, 87 | Folder: ruleSet.folder, 88 | IsMessage: isMessage, 89 | IsPrompt: isPrompt, 90 | IsDynamic: '' + value.includes('{{'), 91 | Name: key, 92 | Value: value 93 | }); 94 | }); 95 | 96 | }); 97 | }); 98 | var output = papa.unparse(csvData, { 99 | quotes: true, 100 | quoteChar: '"', 101 | escapeChar: '"', 102 | delimiter: ",", 103 | header: true, 104 | }) 105 | return output 106 | } 107 | -------------------------------------------------------------------------------- /lambda/GetRuleSetsForExport.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | 7 | /** 8 | * Fetches all rule sets and rules for export 9 | */ 10 | exports.handler = async(event, context) => 11 | { 12 | try 13 | { 14 | requestUtils.logRequest(event); 15 | requestUtils.checkOrigin(event); 16 | var user = await requestUtils.verifyAPIKey(event); 17 | requestUtils.requireRole(user, ['ADMINISTRATOR']); 18 | 19 | var ruleSets = await dynamoUtils.getRuleSetsAndRules(process.env.RULE_SETS_TABLE, process.env.RULES_TABLE); 20 | 21 | // Remove the rule set ids, rule ids and weight ids from the response 22 | ruleSets.forEach(ruleSet => { 23 | 24 | ruleSet.ruleSetId = undefined; 25 | 26 | ruleSet.rules.forEach(rule => { 27 | rule.ruleId = undefined; 28 | rule.ruleSetId = undefined; 29 | 30 | rule.weights.forEach(weight => { 31 | weight.weightId = undefined; 32 | }); 33 | }); 34 | }); 35 | 36 | 37 | return requestUtils.buildSuccessfulResponse({ 38 | ruleSets: ruleSets 39 | }); 40 | } 41 | catch (error) 42 | { 43 | console.log('[ERROR] failed to load rule sets for export', error); 44 | return requestUtils.buildErrorResponse(error); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /lambda/GetSystemHealth.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var connectUtils = require('./utils/ConnectUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Fetches system health of contact flows 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR']); 19 | 20 | await configUtils.checkLastChange(process.env.CONFIG_TABLE); 21 | var configItems = await configUtils.getConfigItems(process.env.CONFIG_TABLE); 22 | 23 | var response = { 24 | systemHealth: { 25 | status: 'HEALTHY' 26 | } 27 | }; 28 | 29 | response.systemHealth.contactFlows = await connectUtils.checkContactFlowStatus(process.env.INSTANCE_ID, 30 | process.env.STAGE, process.env.SERVICE, configItems); 31 | 32 | if (response.systemHealth.contactFlows.status === 'UNHEALTHY') 33 | { 34 | response.systemHealth.status = 'UNHEALTHY'; 35 | } 36 | 37 | return requestUtils.buildSuccessfulResponse(response); 38 | } 39 | catch (error) 40 | { 41 | console.log('[ERROR] failed to fetch system health', error); 42 | return requestUtils.buildErrorResponse(error); 43 | } 44 | }; 45 | 46 | -------------------------------------------------------------------------------- /lambda/GetTests.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | 7 | /** 8 | * Fetches a single test by test id or all tests 9 | */ 10 | exports.handler = async(event, context) => 11 | { 12 | try 13 | { 14 | requestUtils.logRequest(event); 15 | requestUtils.checkOrigin(event); 16 | var user = await requestUtils.verifyAPIKey(event); 17 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 18 | 19 | var tests = []; 20 | var testId = undefined; 21 | 22 | if (event.queryStringParameters !== null) 23 | { 24 | testId = event.queryStringParameters.testId; 25 | } 26 | 27 | if (testId !== undefined) 28 | { 29 | var test = await dynamoUtils.getTest(process.env.TESTS_TABLE, testId); 30 | tests = [ test ]; 31 | } 32 | else 33 | { 34 | tests = await dynamoUtils.getTests(process.env.TESTS_TABLE); 35 | } 36 | 37 | return requestUtils.buildSuccessfulResponse({ 38 | tests: tests 39 | }); 40 | } 41 | catch (error) 42 | { 43 | console.log('[ERROR] failed to load tests', error); 44 | return requestUtils.buildErrorResponse(error); 45 | } 46 | }; 47 | 48 | -------------------------------------------------------------------------------- /lambda/GetUsers.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | 7 | /** 8 | * Fetches all users 9 | */ 10 | exports.handler = async(event, context) => 11 | { 12 | try 13 | { 14 | requestUtils.logRequest(event); 15 | requestUtils.checkOrigin(event); 16 | var user = await requestUtils.verifyAPIKey(event); 17 | requestUtils.requireRole(user, ['ADMINISTRATOR']); 18 | 19 | var users = await dynamoUtils.getUsers(process.env.USERS_TABLE); 20 | 21 | return requestUtils.buildSuccessfulResponse({ 22 | users: users 23 | }); 24 | } 25 | catch (error) 26 | { 27 | console.log('[ERROR] failed to users', error); 28 | return requestUtils.buildErrorResponse(error); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /lambda/ImportRuleSets.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Imports rule sets and rules, clearing all existing rule sets and rules 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.checkOrigin(event); 16 | var user = await requestUtils.verifyAPIKey(event); 17 | requestUtils.requireRole(user, ['ADMINISTRATOR']); 18 | 19 | var body = JSON.parse(event.body); 20 | 21 | var ruleSetsToImport = body.ruleSets; 22 | 23 | console.log('[INFO] about to import rule sets'); 24 | 25 | await dynamoUtils.importRuleSets(process.env.RULE_SETS_TABLE, process.env.RULES_TABLE, 26 | process.env.END_POINTS_TABLE, ruleSetsToImport); 27 | 28 | // Mark the last change to now 29 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 30 | 31 | return requestUtils.buildSuccessfulResponse({ 32 | importCount: ruleSetsToImport.length 33 | }); 34 | } 35 | catch (error) 36 | { 37 | console.log('[ERROR] failed to import rule sets', error); 38 | return requestUtils.buildErrorResponse(error); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /lambda/ImportTests.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Imports tests that are production ready, identifying existing tests based on name and folder 10 | * ignoring non-production ready tests 11 | */ 12 | exports.handler = async(event, context) => 13 | { 14 | try 15 | { 16 | requestUtils.logRequest(event); 17 | requestUtils.checkOrigin(event); 18 | var user = await requestUtils.verifyAPIKey(event); 19 | requestUtils.requireRole(user, ['ADMINISTRATOR']); 20 | 21 | var body = JSON.parse(event.body); 22 | 23 | var testsToImport = body.tests; 24 | 25 | console.info('About to import tests'); 26 | 27 | var result = await dynamoUtils.importTests(process.env.TESTS_TABLE, testsToImport); 28 | 29 | // Mark the last change to now 30 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 31 | 32 | return requestUtils.buildSuccessfulResponse(result); 33 | } 34 | catch (error) 35 | { 36 | console.error('Failed to import tests', error); 37 | return requestUtils.buildErrorResponse(error); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /lambda/MoveRuleSets.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Moves rule sets to a new folder 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 19 | 20 | var body = JSON.parse(event.body); 21 | var ruleSetIds = body.ruleSetIds; 22 | var newFolder = body.newFolder; 23 | 24 | await dynamoUtils.moveRuleSets(process.env.RULE_SETS_TABLE, ruleSetIds, newFolder); 25 | 26 | // Mark the last change to now 27 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 28 | 29 | return requestUtils.buildSuccessfulResponse({ 30 | message: 'Rule sets moved successfully' 31 | }); 32 | } 33 | catch (error) 34 | { 35 | console.log('[ERROR] failed to move rule sets', error); 36 | return requestUtils.buildErrorResponse(error); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /lambda/MoveTests.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Moves tests to a new folder 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 19 | 20 | var body = JSON.parse(event.body); 21 | var testIds = body.testIds; 22 | var newFolder = body.newFolder; 23 | 24 | await dynamoUtils.moveTests(process.env.TESTS_TABLE, testIds, newFolder); 25 | 26 | // Mark the last change to now 27 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 28 | 29 | return requestUtils.buildSuccessfulResponse({ 30 | message: 'Tests moved successfully' 31 | }); 32 | } 33 | catch (error) 34 | { 35 | console.log('[ERROR] failed to move tests', error); 36 | return requestUtils.buildErrorResponse(error); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /lambda/PostImportRuleSets.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Handles post import rule sets including deleting dangling rule sets 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR']); 19 | 20 | var body = JSON.parse(event.body); 21 | 22 | var importedRuleSets = body.importedRuleSets; 23 | 24 | console.log('[INFO] about to perform post import actions'); 25 | 26 | var existingRuleSets = await dynamoUtils.getRuleSetsAndRules(process.env.RULE_SETS_TABLE, process.env.RULES_TABLE); 27 | 28 | var deletedCount = 0; 29 | 30 | for (var i = 0; i < existingRuleSets.length; i++) 31 | { 32 | var existingRuleSet = existingRuleSets[i]; 33 | var newRuleSet = importedRuleSets.find(ruleSetName => ruleSetName === existingRuleSet.name); 34 | 35 | if (newRuleSet === undefined) 36 | { 37 | await dynamoUtils.deleteRuleSetAndRules(process.env.RULE_SETS_TABLE, process.env.RULES_TABLE, existingRuleSet); 38 | deletedCount++; 39 | } 40 | } 41 | 42 | // Mark the last change to now 43 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 44 | 45 | console.log(`[INFO] successfully deleted: ${deletedCount} dangling rule sets`); 46 | 47 | return requestUtils.buildSuccessfulResponse({ 48 | deletedCount: deletedCount 49 | }); 50 | } 51 | catch (error) 52 | { 53 | console.log('[ERROR] failed to perform post import actions', error); 54 | return requestUtils.buildErrorResponse(error); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /lambda/PostImportTests.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Handles post import tests including deleting dangling tests 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR']); 19 | 20 | var body = JSON.parse(event.body); 21 | 22 | var importedTests = body.importedTests; 23 | 24 | console.log('[INFO] about to perform post import actions'); 25 | 26 | var existingTests = await dynamoUtils.getTests(process.env.TESTS_TABLE); 27 | 28 | var deletedCount = 0; 29 | 30 | for (var i = 0; i < existingTests.length; i++) 31 | { 32 | var existingTest = existingTests[i]; 33 | var newTest = importedTests.find(test => (test.folder === existingTest.folder && test.name === existingTest.name)); 34 | 35 | if (newTest === undefined) 36 | { 37 | await dynamoUtils.deleteTest(process.env.TESTS_TABLE, existingTest.testId); 38 | deletedCount++; 39 | } 40 | } 41 | 42 | // Mark the last change to now 43 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 44 | 45 | console.log(`[INFO] successfully deleted: ${deletedCount} dangling tests`); 46 | 47 | return requestUtils.buildSuccessfulResponse({ 48 | deletedCount: deletedCount 49 | }); 50 | } 51 | catch (error) 52 | { 53 | console.log('[ERROR] failed to perform post import actions', error); 54 | return requestUtils.buildErrorResponse(error); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /lambda/RefreshConnectCache.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var lambdaUtils = require('./utils/LambdaUtils.js'); 6 | 7 | /** 8 | * Caches Connect data in DynamoDB, triggered via admin page button 9 | */ 10 | exports.handler = async(event, context) => 11 | { 12 | try 13 | { 14 | requestUtils.logRequest(event); 15 | requestUtils.checkOrigin(event); 16 | var user = await requestUtils.verifyAPIKey(event); 17 | requestUtils.requireRole(user, ['ADMINISTRATOR']); 18 | 19 | await lambdaUtils.invokeAsync(process.env.CACHE_LAMBDA_ARN, ''); 20 | 21 | console.log('[INFO] cache refresh is underway'); 22 | 23 | return requestUtils.buildSuccessfulResponse({ 24 | success: 'true' 25 | }); 26 | } 27 | catch (error) 28 | { 29 | console.log('[ERROR] failed to initialise connect cache refresh', error); 30 | return requestUtils.buildErrorResponse(error); 31 | } 32 | }; 33 | 34 | 35 | -------------------------------------------------------------------------------- /lambda/RenameRule.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var rulesEngine = require('./utils/RulesEngine.js'); 7 | var configUtils = require('./utils/ConfigUtils.js'); 8 | 9 | /** 10 | * Renames a rule 11 | */ 12 | exports.handler = async(event, context) => 13 | { 14 | try 15 | { 16 | requestUtils.logRequest(event); 17 | requestUtils.checkOrigin(event); 18 | var user = await requestUtils.verifyAPIKey(event); 19 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 20 | 21 | var body = JSON.parse(event.body); 22 | 23 | var ruleSetId = body.ruleSetId; 24 | var ruleId = body.ruleId; 25 | var ruleName = body.ruleName; 26 | 27 | // Load the rule set and it's rules 28 | var ruleSet = await dynamoUtils.getRuleSet(process.env.RULE_SETS_TABLE, process.env.RULES_TABLE, ruleSetId); 29 | 30 | if (ruleSet === undefined) 31 | { 32 | throw new Error('Failed to locate rule set for id: ' + ruleSetId); 33 | } 34 | 35 | var rule = ruleSet.rules.find(r => r.ruleId === ruleId); 36 | 37 | if (rule === undefined) 38 | { 39 | throw new Error('Failed to locate rule for id: ' + ruleId); 40 | } 41 | 42 | // Make sure there are no other rules in this ruleset with the new name 43 | var existingRule = ruleSet.rules.find(r => r.name === ruleName); 44 | 45 | if (existingRule !== undefined) 46 | { 47 | return requestUtils.buildFailureResponse(409, { 48 | message: 'A rule already exists with name: ' + ruleName 49 | }); 50 | } 51 | 52 | // Rename the rule 53 | await dynamoUtils.updateRuleName(process.env.RULES_TABLE, ruleSetId, ruleId, ruleName); 54 | 55 | // Mark the last change to now 56 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 57 | 58 | // Success! 59 | return requestUtils.buildSuccessfulResponse({ 60 | message: 'Rule renamed successfully' 61 | }); 62 | } 63 | catch (error) 64 | { 65 | console.log('[ERROR] failed to rename rule', error); 66 | return requestUtils.buildErrorResponse(error); 67 | } 68 | }; 69 | 70 | -------------------------------------------------------------------------------- /lambda/RenameRuleSet.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var rulesEngine = require('./utils/RulesEngine.js'); 7 | var configUtils = require('./utils/ConfigUtils.js'); 8 | 9 | /** 10 | * Renames a rule set and it's dependencies 11 | */ 12 | exports.handler = async(event, context) => 13 | { 14 | try 15 | { 16 | requestUtils.logRequest(event); 17 | requestUtils.checkOrigin(event); 18 | var user = await requestUtils.verifyAPIKey(event); 19 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 20 | 21 | var body = JSON.parse(event.body); 22 | 23 | var ruleSetId = body.ruleSetId; 24 | var ruleSetName = body.ruleSetName; 25 | 26 | // Load all rule sets and rules 27 | var ruleSets = await dynamoUtils.getRuleSetsAndRules(process.env.RULE_SETS_TABLE, process.env.RULES_TABLE); 28 | 29 | // Find this rule set and fail if not found 30 | var ruleSet = ruleSets.find(rs => rs.ruleSetId === ruleSetId); 31 | 32 | if (ruleSet === undefined) 33 | { 34 | throw new Error('Failed to locate rule set for id: ' + ruleSetId); 35 | } 36 | 37 | // If a ruleset exists with this name, error with a nice message 38 | var ruleSetSharedName = ruleSets.find(rs => rs.name === ruleSetName); 39 | 40 | if (ruleSetSharedName !== undefined) 41 | { 42 | return requestUtils.buildFailureResponse(409, { 43 | message: 'A rule set already exists with name: ' + ruleSetName 44 | }); 45 | } 46 | 47 | // Check to see if any rules point to this rule set 48 | var referencingRules = rulesEngine.getReferringRules(ruleSet, ruleSets); 49 | 50 | // Update the rules that point to this and the rule set itself 51 | await dynamoUtils.renameRuleSet(process.env.RULE_SETS_TABLE, process.env.RULES_TABLE, ruleSetName, ruleSet, referencingRules); 52 | 53 | // Mark the last change to now 54 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 55 | 56 | // Success! 57 | return requestUtils.buildSuccessfulResponse({ 58 | message: 'Rule set renamed successfully' 59 | }); 60 | } 61 | catch (error) 62 | { 63 | console.log('[ERROR] failed to rename rule set', error); 64 | return requestUtils.buildErrorResponse(error); 65 | } 66 | }; 67 | 68 | -------------------------------------------------------------------------------- /lambda/SaveHoliday.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | const { v4: uuidv4 } = require('uuid'); 9 | 10 | /** 11 | * Saves a holiday in DynamoDB 12 | */ 13 | exports.handler = async(event, context) => 14 | { 15 | try 16 | { 17 | requestUtils.logRequest(event); 18 | requestUtils.checkOrigin(event); 19 | var user = await requestUtils.verifyAPIKey(event); 20 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 21 | 22 | var body = JSON.parse(event.body); 23 | var holidayId = body.holidayId; 24 | var when = body.when; 25 | var name = body.name; 26 | var description = body.description; 27 | var closed = body.closed; 28 | 29 | await configUtils.checkLastChange(process.env.CONFIG_TABLE); 30 | 31 | var holidays = await configUtils.getHolidays(process.env.CONFIG_TABLE); 32 | 33 | // Find the editing holiday 34 | var existing = holidays.find(holiday => holiday.holidayId === holidayId); 35 | 36 | if (existing === undefined) 37 | { 38 | throw new Error('Failed to find existing holiday to update'); 39 | } 40 | 41 | existing.name = name; 42 | existing.description = description; 43 | existing.closed = closed; 44 | existing.when = when; 45 | 46 | var holidaysToSave = JSON.stringify(holidays); 47 | 48 | await configUtils.updateConfigItem(process.env.CONFIG_TABLE, 'Holidays', holidaysToSave); 49 | 50 | // Mark the last change to now 51 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 52 | 53 | return requestUtils.buildSuccessfulResponse({ 54 | holidayId: holidayId 55 | }); 56 | } 57 | catch (error) 58 | { 59 | console.log('[ERROR] failed to create holiday', error); 60 | return requestUtils.buildErrorResponse(error); 61 | } 62 | }; 63 | 64 | -------------------------------------------------------------------------------- /lambda/UpdateEndPoint.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Updates an existing end point in DyanmoDB 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 19 | 20 | var body = JSON.parse(event.body); 21 | var endPointId = body.endPointId; 22 | var description = body.description; 23 | var inboundNumbers = body.inboundNumbers; 24 | var enabled = body.enabled; 25 | 26 | if (endPointId === undefined) 27 | { 28 | throw new Error('Missing end point id'); 29 | } 30 | 31 | if (description === undefined) 32 | { 33 | description = ''; 34 | } 35 | 36 | if (inboundNumbers === undefined) 37 | { 38 | inboundNumbers = []; 39 | } 40 | 41 | if (enabled === undefined) 42 | { 43 | enabled = false; 44 | } 45 | 46 | await dynamoUtils.updateEndPoint(process.env.END_POINTS_TABLE, 47 | endPointId, description, inboundNumbers, enabled); 48 | 49 | // Mark the last change to now 50 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 51 | 52 | return requestUtils.buildSuccessfulResponse({ 53 | message: 'End point updated successfully' 54 | }); 55 | } 56 | catch (error) 57 | { 58 | console.log('[ERROR] failed to update end point', error); 59 | return requestUtils.buildErrorResponse(error); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /lambda/UpdateRule.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | var {validateRuleParams} = require("./utils/HandlebarsUtils"); 8 | 9 | /** 10 | * Updates an existing rule in DynamoDB 11 | */ 12 | exports.handler = async(event, context) => 13 | { 14 | try 15 | { 16 | requestUtils.logRequest(event); 17 | requestUtils.checkOrigin(event); 18 | var user = await requestUtils.verifyAPIKey(event); 19 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 20 | 21 | var body = JSON.parse(event.body); 22 | var ruleSetId = body.ruleSetId; 23 | var ruleId = body.ruleId; 24 | var ruleEnabled = body.ruleEnabled; 25 | var ruleDescription = body.ruleDescription; 26 | var rulePriority = body.rulePriority; 27 | var ruleActivation = body.ruleActivation; 28 | var ruleType = body.ruleType; 29 | var params = body.params; 30 | 31 | const {valid, lastFailedError} = validateRuleParams(params) 32 | 33 | if (!valid) 34 | { 35 | lastFailedError.statusCode = 400; // Bad request 36 | return requestUtils.buildErrorResponse(lastFailedError); 37 | } 38 | 39 | await dynamoUtils.updateRule(process.env.RULES_TABLE, 40 | ruleSetId, ruleId, ruleEnabled, ruleDescription, rulePriority, ruleActivation, 41 | ruleType, params); 42 | 43 | // Mark the last change to now 44 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 45 | 46 | return requestUtils.buildSuccessfulResponse({ 47 | message: 'Rule updated successfully' 48 | }); 49 | } 50 | catch (error) 51 | { 52 | console.log('[ERROR] failed to update rule', error); 53 | return requestUtils.buildErrorResponse(error); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /lambda/UpdateRuleSet.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Updates an existing rule set in DynamoDB 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 19 | 20 | var body = JSON.parse(event.body); 21 | var ruleSetId = body.ruleSetId; 22 | var ruleSetEnabled = body.ruleSetEnabled; 23 | var ruleSetDescription = body.ruleSetDescription; 24 | var endPoints = body.endPoints; 25 | var folder = body.folder; 26 | 27 | await dynamoUtils.updateRuleSet(process.env.RULE_SETS_TABLE, 28 | ruleSetId, ruleSetEnabled, ruleSetDescription, endPoints, folder); 29 | 30 | // Mark the last change to now 31 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 32 | 33 | return requestUtils.buildSuccessfulResponse({ 34 | message: 'Rule set updated successfully' 35 | }); 36 | } 37 | catch (error) 38 | { 39 | console.log('[ERROR] failed to update rule set', error); 40 | return requestUtils.buildErrorResponse(error); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /lambda/UpdateTest.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Updates an existing test in DynamoDB 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER', 'TESTER']); 19 | 20 | var body = JSON.parse(event.body); 21 | var testId = body.testId; 22 | var name = body.name; 23 | var productionReady = body.productionReady; 24 | var folder = body.folder; 25 | var testReference = body.testReference; 26 | var description = body.description; 27 | var endPoint = body.endPoint; 28 | var testDateTime = body.testDateTime; 29 | var customerPhoneNumber = body.customerPhoneNumber; 30 | var payload = body.payload; 31 | var contactAttributes = body.contactAttributes; 32 | 33 | await dynamoUtils.updateTest(process.env.TESTS_TABLE, testId, name, 34 | productionReady, folder, testReference, description, endPoint, 35 | testDateTime, customerPhoneNumber, payload, contactAttributes); 36 | 37 | // Mark the last change to now 38 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 39 | 40 | return requestUtils.buildSuccessfulResponse({ 41 | message: 'Test updated successfully' 42 | }); 43 | } 44 | catch (error) 45 | { 46 | console.log('[ERROR] failed to update test', error); 47 | return requestUtils.buildErrorResponse(error); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /lambda/UpdateUser.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | /** 9 | * Updates an existing user in DyanmoDB 10 | */ 11 | exports.handler = async(event, context) => 12 | { 13 | try 14 | { 15 | requestUtils.logRequest(event); 16 | requestUtils.checkOrigin(event); 17 | var user = await requestUtils.verifyAPIKey(event); 18 | requestUtils.requireRole(user, ['ADMINISTRATOR']); 19 | 20 | var body = JSON.parse(event.body); 21 | var userId = body.userId; 22 | var userEnabled = body.userEnabled; 23 | var emailAddress = body.emailAddress; 24 | var firstName = body.firstName; 25 | var lastName = body.lastName; 26 | var userRole = body.userRole; 27 | var apiKey = body.apiKey; 28 | 29 | // Check for an existing user with this email 30 | var existingUser = await dynamoUtils.getUserByEmailAddress(process.env.USERS_TABLE, emailAddress); 31 | 32 | if (existingUser !== undefined && existingUser.userId !== userId) 33 | { 34 | console.log('[ERROR] another user already exists with this email address: ' + emailAddress); 35 | 36 | return requestUtils.buildFailureResponse(409, { 37 | message: 'User already exists for email' 38 | }); 39 | } 40 | 41 | // Check for an existing user with this API key if we got a new API key 42 | if (apiKey !== '') 43 | { 44 | existingUser = await dynamoUtils.getUserByAPIKey(process.env.USERS_TABLE, apiKey); 45 | if (existingUser !== undefined && existingUser.userId !== userId) 46 | { 47 | console.log('[ERROR] another user already exists with this API key: ' + apiKey); 48 | 49 | return requestUtils.buildFailureResponse(409, { 50 | message: 'User already exists with API key' 51 | }); 52 | } 53 | } 54 | 55 | await dynamoUtils.updateUser(process.env.USERS_TABLE, 56 | userId, firstName, lastName, emailAddress, userRole, apiKey, userEnabled); 57 | 58 | // Mark the last change to now 59 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 60 | 61 | return requestUtils.buildSuccessfulResponse({ 62 | message: 'User updated successfully' 63 | }); 64 | } 65 | catch (error) 66 | { 67 | console.log('[ERROR] failed to update user', error); 68 | return requestUtils.buildErrorResponse(error); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /lambda/UpdateWeight.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | var dynamoUtils = require('./utils/DynamoUtils.js'); 6 | var configUtils = require('./utils/ConfigUtils.js'); 7 | 8 | const { v4: uuidv4 } = require('uuid'); 9 | 10 | /** 11 | * update a weight for a rule in DynamoDB 12 | */ 13 | exports.handler = async(event, context) => 14 | { 15 | try 16 | { 17 | requestUtils.logRequest(event); 18 | requestUtils.checkOrigin(event); 19 | var user = await requestUtils.verifyAPIKey(event); 20 | requestUtils.requireRole(user, ['ADMINISTRATOR', 'POWER_USER']); 21 | 22 | var body = JSON.parse(event.body); 23 | 24 | var {ruleSetId, ruleId, field,weightId,operation,value,weight} = body; 25 | 26 | await dynamoUtils.updateWeight(process.env.RULES_TABLE, ruleSetId, ruleId,weightId, field,operation,value,weight); 27 | 28 | 29 | // Mark the last change to now 30 | await configUtils.setLastChangeTimestampToNow(process.env.CONFIG_TABLE); 31 | 32 | return requestUtils.buildSuccessfulResponse({ 33 | message: 'Weight updated successfully' 34 | }); 35 | } 36 | catch (error) 37 | { 38 | console.log('[ERROR] failed to update weight', error); 39 | return requestUtils.buildErrorResponse(error); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /lambda/VerifyLogin.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var requestUtils = require('./utils/RequestUtils.js'); 5 | 6 | /** 7 | * Checks the API key for a customer 8 | */ 9 | exports.handler = async(event, context) => 10 | { 11 | try 12 | { 13 | // requestUtils.logRequest(event); 14 | requestUtils.checkOrigin(event); 15 | var user = await requestUtils.verifyAPIKey(event); 16 | return requestUtils.buildSuccessfulResponse({user: user}); 17 | } 18 | catch (error) 19 | { 20 | console.error('Failed to verify login', error); 21 | return requestUtils.buildErrorResponse(error); 22 | } 23 | }; 24 | 25 | 26 | -------------------------------------------------------------------------------- /lambda/interactive/Distribution.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Interactive rule component definition exposing the following 6 | * lifecycle methods: 7 | * 8 | * execute() called on first invocation (required) 9 | * input() called after input on provided (optional) 10 | * confirm() called after confirmation is provided (optional) 11 | * 12 | * The context parameter contains: 13 | * 14 | * - requestMessage: the current request message 15 | * - currentRuleSet: the current rule set 16 | * - currentRule: the current rule 17 | * - customerState: the current customer state 18 | * - stateToSave: A set containing the state fields to persist 19 | */ 20 | 21 | var inferenceUtils = require('../utils/InferenceUtils.js'); 22 | 23 | /** 24 | * Executes Distribution 25 | */ 26 | module.exports.execute = async (context) => 27 | { 28 | try 29 | { 30 | console.info('Distribution.execute() fired'); 31 | 32 | if (context.requestMessage === undefined || context.customerState === undefined || 33 | context.currentRuleSet === undefined || context.currentRule === undefined) 34 | { 35 | throw new Error('Distribution.execute() missing required config'); 36 | } 37 | 38 | var nextRuleSet = inferenceUtils.solveDistribution(context.customerState); 39 | 40 | // Save the current rule set name 41 | inferenceUtils.updateStateContext(context, 'NextRuleSet', nextRuleSet); 42 | 43 | return { 44 | contactId: context.requestMessage.contactId, 45 | inputRequired: false, 46 | ruleSet: context.currentRuleSet.name, 47 | rule: context.currentRule.name, 48 | ruleType: context.currentRule.type 49 | }; 50 | } 51 | catch (error) 52 | { 53 | console.error('Distribution.execute() failed: ' + error.message); 54 | throw error; 55 | } 56 | }; 57 | 58 | /** 59 | * Executes Distribution input 60 | */ 61 | module.exports.input = async (context) => 62 | { 63 | console.error('Distribution.input() is not implemented'); 64 | throw new Error('Distribution.input() is not implemented'); 65 | }; 66 | 67 | /** 68 | * Executes Distribution confirm 69 | */ 70 | module.exports.confirm = async (context) => 71 | { 72 | console.error('Distribution.confirm() is not implemented'); 73 | throw new Error('Distribution.confirm() is not implemented'); 74 | }; 75 | -------------------------------------------------------------------------------- /lambda/interactive/ExternalNumber.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Interactive rule component definition exposing the following 6 | * lifecycle methods: 7 | * 8 | * execute() called on first invocation (required) 9 | * input() called after input on provided (optional) 10 | * confirm() called after confirmation is provided (optional) 11 | * 12 | * The context parameter contains: 13 | * 14 | * - requestMessage: the current request message 15 | * - currentRuleSet: the current rule set 16 | * - currentRule: the current rule 17 | * - customerState: the current customer state 18 | * - stateToSave: A set containing the state fields to persist 19 | */ 20 | 21 | var inferenceUtils = require('../utils/InferenceUtils.js'); 22 | 23 | /** 24 | * Executes ExternalNumber 25 | */ 26 | module.exports.execute = async (context) => 27 | { 28 | try 29 | { 30 | if (context.requestMessage === undefined || 31 | context.customerState === undefined || 32 | context.currentRuleSet === undefined || 33 | context.currentRule === undefined || 34 | context.customerState.CurrentRule_externalNumber === undefined) 35 | { 36 | throw new Error('ExternalNumber.execute() missing required config'); 37 | } 38 | 39 | var externalNumber = context.customerState.CurrentRule_externalNumber; 40 | 41 | if (context.customerState.CurrentRule_resume === 'true') 42 | { 43 | return { 44 | contactId: context.requestMessage.contactId, 45 | inputRequired: false, 46 | ruleSet: context.currentRuleSet.name, 47 | rule: context.currentRule.name, 48 | ruleType: context.currentRule.type, 49 | externalNumber: externalNumber, 50 | resume: true 51 | }; 52 | } 53 | else 54 | { 55 | return { 56 | contactId: context.requestMessage.contactId, 57 | inputRequired: false, 58 | ruleSet: context.currentRuleSet.name, 59 | rule: context.currentRule.name, 60 | ruleType: context.currentRule.type, 61 | externalNumber: externalNumber, 62 | resume: false 63 | }; 64 | } 65 | } 66 | catch (error) 67 | { 68 | console.error('ExternalNumber.execute() failed: ' + error.message); 69 | throw error; 70 | } 71 | }; 72 | 73 | /** 74 | * Executes ExternalNumber input 75 | */ 76 | module.exports.input = async (context) => 77 | { 78 | console.error('ExternalNumber.input() is not implemented'); 79 | throw new Error('ExternalNumber.input() is not implemented'); 80 | }; 81 | 82 | /** 83 | * Executes ExternalNumber confirm 84 | */ 85 | module.exports.confirm = async (context) => 86 | { 87 | console.error('ExternalNumber.confirm() is not implemented'); 88 | throw new Error('ExternalNumber.confirm() is not implemented'); 89 | }; 90 | -------------------------------------------------------------------------------- /lambda/interactive/Message.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Interactive rule component definition exposing the following 6 | * lifecycle methods: 7 | * 8 | * execute() called on first invocation (required) 9 | * input() called after input on provided (optional) 10 | * confirm() called after confirmation is provided (optional) 11 | * 12 | * The context parameter contains: 13 | * 14 | * - requestMessage: the current request message 15 | * - currentRuleSet: the current rule set 16 | * - currentRule: the current rule 17 | * - customerState: the current customer state 18 | * - stateToSave: A set containing the state fields to persist 19 | */ 20 | 21 | var inferenceUtils = require('../utils/InferenceUtils.js'); 22 | 23 | /** 24 | * Executes Message 25 | */ 26 | module.exports.execute = async (context) => 27 | { 28 | try 29 | { 30 | var message = context.customerState.CurrentRule_message; 31 | 32 | if (message === undefined || 33 | message === '') 34 | { 35 | throw new Error('Message.execute() missing required config'); 36 | } 37 | 38 | return { 39 | contactId: context.requestMessage.contactId, 40 | inputRequired: false, 41 | message: message, 42 | ruleSet: context.currentRuleSet.name, 43 | rule: context.currentRule.name, 44 | ruleType: context.currentRule.type, 45 | audio: await inferenceUtils.renderVoice(context.requestMessage, message) 46 | }; 47 | } 48 | catch (error) 49 | { 50 | console.error('Message.execute() failed: ' + error.message); 51 | throw error; 52 | } 53 | }; 54 | 55 | /** 56 | * Executes Message input 57 | */ 58 | module.exports.input = async (context) => 59 | { 60 | console.error('Message.input() is not implemented'); 61 | throw new Error('Message.input() is not implemented'); 62 | }; 63 | 64 | /** 65 | * Executes Message confirm 66 | */ 67 | module.exports.confirm = async (context) => 68 | { 69 | console.error('Message.confirm() is not implemented'); 70 | throw new Error('Message.confirm() is not implemented'); 71 | }; 72 | -------------------------------------------------------------------------------- /lambda/interactive/Metric.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Interactive rule component definition exposing the following 6 | * lifecycle methods: 7 | * 8 | * execute() called on first invocation (required) 9 | * input() called after input on provided (optional) 10 | * confirm() called after confirmation is provided (optional) 11 | * 12 | * The context parameter contains: 13 | * 14 | * - requestMessage: the current request message 15 | * - currentRuleSet: the current rule set 16 | * - currentRule: the current rule 17 | * - customerState: the current customer state 18 | * - stateToSave: A set containing the state fields to persist 19 | */ 20 | 21 | var inferenceUtils = require('../utils/InferenceUtils.js'); 22 | 23 | /** 24 | * Executes Metric 25 | */ 26 | module.exports.execute = async (context) => 27 | { 28 | try 29 | { 30 | if (context.requestMessage === undefined || 31 | context.customerState === undefined || 32 | context.currentRuleSet === undefined || 33 | context.currentRule === undefined || 34 | context.customerState.CurrentRule_metricName === undefined || 35 | context.customerState.CurrentRule_metricName === '' || 36 | context.customerState.CurrentRule_metricValue === undefined || 37 | context.customerState.CurrentRule_metricValue === '') 38 | { 39 | throw new Error('Metric.execute() missing required config'); 40 | } 41 | 42 | var metricName = context.customerState.CurrentRule_metricName; 43 | var metricValue = context.customerState.CurrentRule_metricValue; 44 | 45 | return { 46 | contactId: context.requestMessage.contactId, 47 | inputRequired: false, 48 | message: `Metric: ${metricName} Value: ${metricValue}`, 49 | ruleSet: context.currentRuleSet.name, 50 | rule: context.currentRule.name, 51 | ruleType: context.currentRule.type 52 | }; 53 | } 54 | catch (error) 55 | { 56 | console.error('Metric.execute() failed: ' + error.message); 57 | throw error; 58 | } 59 | }; 60 | 61 | /** 62 | * Executes Metric input 63 | */ 64 | module.exports.input = async (context) => 65 | { 66 | console.error('Metric.input() is not implemented'); 67 | throw new Error('Metric.input() is not implemented'); 68 | }; 69 | 70 | /** 71 | * Executes Metric confirm 72 | */ 73 | module.exports.confirm = async (context) => 74 | { 75 | console.error('Metric.confirm() is not implemented'); 76 | throw new Error('Metric.confirm() is not implemented'); 77 | }; 78 | -------------------------------------------------------------------------------- /lambda/interactive/Queue.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Interactive rule component definition exposing the following 6 | * lifecycle methods: 7 | * 8 | * execute() called on first invocation (required) 9 | * input() called after input on provided (optional) 10 | * confirm() called after confirmation is provided (optional) 11 | * 12 | * The context parameter contains: 13 | * 14 | * - requestMessage: the current request message 15 | * - currentRuleSet: the current rule set 16 | * - currentRule: the current rule 17 | * - customerState: the current customer state 18 | * - stateToSave: A set containing the state fields to persist 19 | */ 20 | 21 | var inferenceUtils = require('../utils/InferenceUtils.js'); 22 | 23 | /** 24 | * Executes Queue 25 | * TODO handle hours of operation 26 | */ 27 | module.exports.execute = async (context) => 28 | { 29 | try 30 | { 31 | if (context.requestMessage === undefined || 32 | context.customerState === undefined || 33 | context.currentRuleSet === undefined || 34 | context.currentRule === undefined || 35 | context.customerState.CurrentRule_queueName === undefined) 36 | { 37 | throw new Error('Queue.execute() missing required config'); 38 | } 39 | 40 | var queueName = context.customerState.CurrentRule_queueName; 41 | var message = context.customerState.CurrentRule_message; 42 | 43 | var response = { 44 | contactId: context.requestMessage.contactId, 45 | inputRequired: false, 46 | ruleSet: context.currentRuleSet.name, 47 | rule: context.currentRule.name, 48 | ruleType: context.currentRule.type, 49 | queue: queueName 50 | }; 51 | 52 | if (message !== undefined && message !== '') 53 | { 54 | response.message = message; 55 | response.audio = await inferenceUtils.renderVoice(context.requestMessage, message); 56 | } 57 | 58 | return response; 59 | } 60 | catch (error) 61 | { 62 | console.error('Queue.execute() failed: ' + error.message); 63 | throw error; 64 | } 65 | }; 66 | 67 | /** 68 | * Executes Queue input 69 | */ 70 | module.exports.input = async (context) => 71 | { 72 | console.error('Queue.input() is not implemented'); 73 | throw new Error('Queue.input() is not implemented'); 74 | }; 75 | 76 | /** 77 | * Executes Queue confirm 78 | */ 79 | module.exports.confirm = async (context) => 80 | { 81 | console.error('Queue.confirm() is not implemented'); 82 | throw new Error('Queue.confirm() is not implemented'); 83 | }; 84 | -------------------------------------------------------------------------------- /lambda/interactive/SMSMessage.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Interactive rule component definition exposing the following 6 | * lifecycle methods: 7 | * 8 | * execute() called on first invocation (required) 9 | * input() called after input on provided (optional) 10 | * confirm() called after confirmation is provided (optional) 11 | * 12 | * The context parameter contains: 13 | * 14 | * - requestMessage: the current request message 15 | * - currentRuleSet: the current rule set 16 | * - currentRule: the current rule 17 | * - customerState: the current customer state 18 | * - stateToSave: A set containing the state fields to persist 19 | */ 20 | 21 | var inferenceUtils = require('../utils/InferenceUtils.js'); 22 | 23 | /** 24 | * Executes SMSMessage 25 | */ 26 | module.exports.execute = async (context) => 27 | { 28 | try 29 | { 30 | if (context.requestMessage === undefined || 31 | context.customerState === undefined || 32 | context.currentRuleSet === undefined || 33 | context.currentRule === undefined || 34 | context.customerState.CurrentRule_message === undefined || 35 | context.customerState.CurrentRule_phoneNumberKey === undefined) 36 | { 37 | throw new Error('SMSMessage.execute() missing required config'); 38 | } 39 | 40 | // Phone number to use is in state keyed by CurrentRule_phoneNumberKey 41 | var message = context.customerState.CurrentRule_message; 42 | var phoneNumberKey = context.customerState.CurrentRule_phoneNumberKey; 43 | var phoneNumber = inferenceUtils.getStateValue(context.customerState, phoneNumberKey); 44 | 45 | if (phoneNumber === undefined) 46 | { 47 | console.error('SMSMessage.execute() could not locate phone number with state key: ' + phoneNumberKey); 48 | inferenceUtils.updateStateContext(context, 'System.LastSMSStatus', 'ERROR'); 49 | } 50 | else 51 | { 52 | inferenceUtils.updateStateContext(context, 'System.LastSMSStatus', 'SENT'); 53 | } 54 | 55 | return { 56 | contactId: context.requestMessage.contactId, 57 | inputRequired: false, 58 | message: `SMS: ${phoneNumber} Message: ${message}`, 59 | ruleSet: context.currentRuleSet.name, 60 | rule: context.currentRule.name, 61 | ruleType: context.currentRule.type 62 | }; 63 | } 64 | catch (error) 65 | { 66 | console.error('SMSMessage.execute() failed: ' + error.message); 67 | throw error; 68 | } 69 | }; 70 | 71 | /** 72 | * Executes SMSMessage input 73 | */ 74 | module.exports.input = async (context) => 75 | { 76 | console.error('SMSMessage.input() is not implemented'); 77 | throw new Error('SMSMessage.input() is not implemented'); 78 | }; 79 | 80 | /** 81 | * Executes SMSMessage confirm 82 | */ 83 | module.exports.confirm = async (context) => 84 | { 85 | console.error('SMSMessage.confirm() is not implemented'); 86 | throw new Error('SMSMessage.confirm() is not implemented'); 87 | }; 88 | -------------------------------------------------------------------------------- /lambda/interactive/SetAttributes.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Interactive rule component definition exposing the following 6 | * lifecycle methods: 7 | * 8 | * execute() called on first invocation (required) 9 | * input() called after input on provided (optional) 10 | * confirm() called after confirmation is provided (optional) 11 | * 12 | * The context parameter contains: 13 | * 14 | * - requestMessage: the current request message 15 | * - currentRuleSet: the current rule set 16 | * - currentRule: the current rule 17 | * - customerState: the current customer state 18 | * - stateToSave: A set containing the state fields to persist 19 | */ 20 | 21 | const commonUtils = require('../utils/CommonUtils'); 22 | 23 | /** 24 | * Executes SetAttributes and updates the ContactAttributes in state for a customer 25 | * sourcing the configuration from the context.currentRule.params.setAttributes array 26 | */ 27 | module.exports.execute = async (context) => 28 | { 29 | try 30 | { 31 | if (context.requestMessage === undefined || 32 | context.customerState === undefined || 33 | context.currentRuleSet === undefined || 34 | context.currentRule === undefined || 35 | context.currentRule.params === undefined || 36 | context.currentRule.params.setAttributes === undefined) 37 | { 38 | throw new Error('SetAttributes.execute() missing required config'); 39 | } 40 | 41 | console.info('SetAttributes.execute() Got current rule: ' + JSON.stringify(context.currentRule, null, 2)); 42 | 43 | if (context.customerState.ContactAttributes === undefined) 44 | { 45 | context.customerState.ContactAttributes = {}; 46 | } 47 | 48 | context.currentRule.params.setAttributes.forEach(attribute => 49 | { 50 | if (commonUtils.isNullOrUndefined(attribute.value)) 51 | { 52 | context.customerState.ContactAttributes[attribute.key] = undefined; 53 | } 54 | else 55 | { 56 | context.customerState.ContactAttributes[attribute.key] = attribute.value; 57 | } 58 | }); 59 | 60 | // Request contact attributes to be saved 61 | context.stateToSave.add('ContactAttributes'); 62 | 63 | return { 64 | contactId: context.requestMessage.contactId, 65 | inputRequired: false, 66 | ruleSet: context.currentRuleSet.name, 67 | rule: context.currentRule.name, 68 | ruleType: context.currentRule.type 69 | }; 70 | } 71 | catch (error) 72 | { 73 | console.error('SetAttributes.execute() failed: ' + error.message); 74 | throw error; 75 | } 76 | }; 77 | 78 | /** 79 | * Executes SetAttributes input 80 | */ 81 | module.exports.input = async (context) => 82 | { 83 | console.error('SetAttributes.input() is not implemented'); 84 | throw new Error('SetAttributes.input() is not implemented'); 85 | }; 86 | 87 | /** 88 | * Executes SetAttributes confirm 89 | */ 90 | module.exports.confirm = async (context) => 91 | { 92 | console.error('SetAttributes.confirm() is not implemented'); 93 | throw new Error('SetAttributes.confirm() is not implemented'); 94 | }; 95 | -------------------------------------------------------------------------------- /lambda/interactive/Terminate.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Interactive rule component definition exposing the following 6 | * lifecycle methods: 7 | * 8 | * execute() called on first invocation (required) 9 | * input() called after input on provided (optional) 10 | * confirm() called after confirmation is provided (optional) 11 | * 12 | * The context parameter contains: 13 | * 14 | * - requestMessage: the current request message 15 | * - currentRuleSet: the current rule set 16 | * - currentRule: the current rule 17 | * - customerState: the current customer state 18 | * - stateToSave: A set containing the state fields to persist 19 | */ 20 | 21 | var inferenceUtils = require('../utils/InferenceUtils.js'); 22 | 23 | /** 24 | * Executes Terminate 25 | */ 26 | module.exports.execute = async (context) => 27 | { 28 | try 29 | { 30 | if (context.requestMessage === undefined || 31 | context.customerState === undefined || 32 | context.currentRuleSet === undefined || 33 | context.currentRule === undefined) 34 | { 35 | throw new Error('Terminate.execute() missing required config'); 36 | } 37 | 38 | return { 39 | contactId: context.requestMessage.contactId, 40 | inputRequired: false, 41 | terminate: true, 42 | ruleSet: context.currentRuleSet.name, 43 | rule: context.currentRule.name, 44 | ruleType: context.currentRule.type 45 | }; 46 | } 47 | catch (error) 48 | { 49 | console.error('Terminate.execute() failed: ' + error.message); 50 | throw error; 51 | } 52 | }; 53 | 54 | /** 55 | * Executes Terminate input 56 | */ 57 | module.exports.input = async (context) => 58 | { 59 | console.error('Terminate.input() is not implemented'); 60 | throw new Error('Terminate.input() is not implemented'); 61 | }; 62 | 63 | /** 64 | * Executes Terminate confirm 65 | */ 66 | module.exports.confirm = async (context) => 67 | { 68 | console.error('Terminate.confirm() is not implemented'); 69 | throw new Error('Terminate.confirm() is not implemented'); 70 | }; 71 | -------------------------------------------------------------------------------- /lambda/interactive/UpdateStates.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Interactive rule component definition exposing the following 6 | * lifecycle methods: 7 | * 8 | * execute() called on first invocation (required) 9 | * input() called after input on provided (optional) 10 | * confirm() called after confirmation is provided (optional) 11 | * 12 | * The context parameter contains: 13 | * 14 | * - requestMessage: the current request message 15 | * - currentRuleSet: the current rule set 16 | * - currentRule: the current rule 17 | * - customerState: the current customer state 18 | * - stateToSave: A set containing the state fields to persist 19 | */ 20 | 21 | const inferenceUtils = require('../utils/InferenceUtils'); 22 | const commonUtils = require('../utils/CommonUtils'); 23 | 24 | /** 25 | * Executes UpdateStates 26 | */ 27 | module.exports.execute = async (context) => 28 | { 29 | try 30 | { 31 | if (context.requestMessage === undefined || 32 | context.customerState === undefined || 33 | context.currentRuleSet === undefined || 34 | context.currentRule === undefined || 35 | context.customerState.CurrentRule_updateStates === undefined || 36 | !Array.isArray(context.customerState.CurrentRule_updateStates) || 37 | context.customerState.CurrentRule_updateStates.length === 0) 38 | { 39 | throw new Error('UpdateStates.execute() missing required config'); 40 | } 41 | 42 | for (var i = 0; i < context.customerState.CurrentRule_updateStates.length; i++) 43 | { 44 | var stateKey = context.customerState.CurrentRule_updateStates[i].key; 45 | var stateValue = context.customerState.CurrentRule_updateStates[i].value; 46 | 47 | if (stateValue === 'increment') 48 | { 49 | inferenceUtils.incrementStateValue(context.customerState, context.stateToSave, stateKey); 50 | } 51 | else 52 | { 53 | inferenceUtils.updateStateContext(context, stateKey, stateValue); 54 | } 55 | } 56 | 57 | return { 58 | contactId: context.requestMessage.contactId, 59 | inputRequired: false, 60 | ruleSet: context.currentRuleSet.name, 61 | rule: context.currentRule.name, 62 | ruleType: context.currentRule.type 63 | }; 64 | } 65 | catch (error) 66 | { 67 | console.error('UpdateStates.execute() failed: ' + error.message); 68 | throw error; 69 | } 70 | }; 71 | 72 | /** 73 | * Executes UpdateStates input 74 | */ 75 | module.exports.input = async (context) => 76 | { 77 | console.error('UpdateStates.input() is not implemented'); 78 | throw new Error('UpdateStates.input() is not implemented'); 79 | }; 80 | 81 | /** 82 | * Executes UpdateStates confirm 83 | */ 84 | module.exports.confirm = async (context) => 85 | { 86 | console.error('UpdateStates.confirm() is not implemented'); 87 | throw new Error('UpdateStates.confirm() is not implemented'); 88 | }; 89 | -------------------------------------------------------------------------------- /lambda/utils/BackoffUtils.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /** 5 | * Sleeps for a jittered retry time with exponential backoff 6 | * random sleep time determined between min sleep time (250ms) 7 | * and current clamped amx sleep time (16000ms) 8 | */ 9 | module.exports.backoff = async (context, retry, error) => 10 | { 11 | var sleepTime = computeSleepTime(retry); 12 | console.info(`Backing off: ${context} at retry: ${retry} sleeping for: ${sleepTime} due to: ${error.message}`); 13 | await backoffSleep(sleepTime); 14 | }; 15 | 16 | /** 17 | * Compute a jittered sleep time for a retry between 18 | * the min sleep time and computed exponential vackoff 19 | */ 20 | function computeSleepTime(retry) 21 | { 22 | var baseTime = 250; 23 | var scaling = 2; 24 | var clampedRetry = Math.min(retry, 5); 25 | var maxWaitTime = baseTime * Math.pow(scaling, clampedRetry); 26 | var actualWaitTime = Math.floor(maxWaitTime * Math.random()); 27 | return Math.max(baseTime, actualWaitTime); 28 | } 29 | 30 | /** 31 | * Sleep for requested time in millis 32 | */ 33 | function backoffSleep(time) 34 | { 35 | return new Promise((resolve) => setTimeout(resolve, time)); 36 | } 37 | -------------------------------------------------------------------------------- /lambda/utils/CloudWatchUtils.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var AWS = require('aws-sdk'); 5 | AWS.config.update({region: process.env.REGION}); 6 | var cloudWatch = new AWS.CloudWatch(); 7 | 8 | /** 9 | * Records a cloud watch custom metric 10 | */ 11 | module.exports.putMetricData = async function(stage, namespace, metricName, metricValue) 12 | { 13 | try 14 | { 15 | var metrics = []; 16 | 17 | metrics.push({ 18 | 'MetricName': metricName, 19 | 'Dimensions': [{ 20 | 'Name': 'Environment', 21 | 'Value': stage 22 | }], 23 | 'Unit': 'Count', 24 | 'Value': +metricValue 25 | }); 26 | 27 | var putMetricsRequest = { 28 | Namespace: namespace, 29 | MetricData: metrics 30 | }; 31 | 32 | await cloudWatch.putMetricData(putMetricsRequest).promise(); 33 | } 34 | catch (error) 35 | { 36 | console.log('[ERROR] failed to put CloudWatch metrics', error); 37 | throw error; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lambda/utils/CommonUtils.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var moment = require('moment'); 5 | 6 | /** 7 | * Checks to see if value is a number 8 | */ 9 | module.exports.isNumber = (value) => 10 | { 11 | return !isNaN(parseFloat(value)) && isFinite(value); 12 | }; 13 | 14 | /** 15 | * Sleep for time millis 16 | */ 17 | module.exports.sleep = (time) => 18 | { 19 | return new Promise((resolve) => setTimeout(resolve, time)); 20 | }; 21 | 22 | /** 23 | * Checks to see if this value undefined, null or the empty string 24 | */ 25 | module.exports.isEmptyString = (value) => 26 | { 27 | if (value === undefined || 28 | value === null || 29 | value === '') 30 | { 31 | return true; 32 | } 33 | 34 | return false; 35 | }; 36 | 37 | /** 38 | * Returns true if the value is null or undefined or equal to the string 39 | * 'null' or 'undefined' 40 | */ 41 | module.exports.isNullOrUndefined = (value) => 42 | { 43 | if (value === undefined || 44 | value === null || 45 | value === 'null' || 46 | value === 'undefined') 47 | { 48 | return true; 49 | } 50 | else 51 | { 52 | return false; 53 | } 54 | }; 55 | 56 | /** 57 | * Get a now timestamp to the millisecond 58 | */ 59 | module.exports.nowUTCMillis = () => 60 | { 61 | return moment().utc().format('YYYY-MM-DDTHH:mm:ss.SSSZ'); 62 | }; 63 | 64 | /** 65 | * Clones an object 66 | */ 67 | module.exports.clone = (object) => 68 | { 69 | return JSON.parse(JSON.stringify(object)); 70 | }; 71 | 72 | /** 73 | * Safely merges an array of prompts, handling one or more being SSML 74 | */ 75 | module.exports.safelyMergePrompts = (promptsArray) => 76 | { 77 | var hasSSML = false; 78 | 79 | promptsArray.forEach(prompt => 80 | { 81 | if (module.exports.isSSML(prompt)) 82 | { 83 | hasSSML = true; 84 | } 85 | }); 86 | 87 | var outputPrompt = ''; 88 | 89 | if (hasSSML) 90 | { 91 | outputPrompt += ''; 92 | } 93 | 94 | promptsArray.forEach(prompt => 95 | { 96 | outputPrompt += `\n${module.exports.stripSSMLWrapper(prompt)}`; 97 | }); 98 | 99 | if (hasSSML) 100 | { 101 | outputPrompt += '\n'; 102 | } 103 | 104 | outputPrompt = outputPrompt.trim(); 105 | 106 | return outputPrompt; 107 | } 108 | 109 | 110 | /** 111 | * Checks to see if this is an SSML tag 112 | */ 113 | module.exports.isSSML = (prompt) => 114 | { 115 | var trimmed = prompt.trim(); 116 | return trimmed === '' || (trimmed.startsWith('') && trimmed.endsWith('')); 117 | } 118 | 119 | /** 120 | * Strips wrapper speak tags 121 | */ 122 | module.exports.stripSSMLWrapper = (prompt) => 123 | { 124 | if (module.exports.isSSML(prompt)) 125 | { 126 | return prompt.replace('', '').replace('', '').trim(); 127 | } 128 | else 129 | { 130 | return prompt; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lambda/utils/ErrorCodeUtils.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | class UnauthorizedError extends Error{ 5 | statusCode = 401; 6 | } 7 | class ForbiddenError extends Error{ 8 | statusCode = 403; 9 | } 10 | class BadRequestError extends Error{ 11 | statusCode = 400; 12 | } 13 | class UnhandledError extends Error{ 14 | statusCode = 500; 15 | } 16 | module.exports = { 17 | UnauthorizedError,ForbiddenError,BadRequestError,UnhandledError 18 | } 19 | -------------------------------------------------------------------------------- /lambda/utils/KeepWarmUtils.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const commonUtils = require('./CommonUtils'); 5 | const moment = require('moment'); 6 | const { v4: uuidv4 } = require('uuid'); 7 | 8 | var warmTimestamp = undefined; 9 | var functionId = undefined; 10 | 11 | /** 12 | * Resets for testing purposes 13 | */ 14 | module.exports.reset = function() 15 | { 16 | warmTimestamp = undefined; 17 | functionId = undefined; 18 | } 19 | 20 | /** 21 | * Creates a keep warm request for a function 22 | */ 23 | module.exports.createKeepWarmRequest = function(name, arn) 24 | { 25 | var request = { 26 | keepWarm: { 27 | name: name, 28 | arn: arn, 29 | } 30 | }; 31 | return request; 32 | } 33 | 34 | /** 35 | * Checks for a health check request which has the structure from createKeepWarmRequest() 36 | */ 37 | module.exports.isKeepWarmRequest = function(request) 38 | { 39 | if (request.keepWarm !== undefined && 40 | request.keepWarm.name !== undefined && 41 | request.keepWarm.arn !== undefined) 42 | { 43 | console.info(`Detected keep warm request for function: ${request.keepWarm.name}`); 44 | return true; 45 | } 46 | 47 | return false; 48 | } 49 | 50 | /** 51 | * Checks for a health check response which has the structure from makeKeepWarmResponse() 52 | */ 53 | module.exports.isKeepWarmResponse = function(response) 54 | { 55 | if (response.keepWarm !== undefined && 56 | response.keepWarm.name !== undefined && 57 | response.keepWarm.arn !== undefined) 58 | { 59 | return true; 60 | } 61 | 62 | console.info(`Detected non keep warm response: ${JSON.stringify(response, null, 2)}`); 63 | 64 | return false; 65 | } 66 | 67 | /** 68 | * Creates a keep warm response, tracking cold starts via the warmTimestamp field 69 | * and providing an optional sleep time for warm functions 70 | */ 71 | module.exports.makeKeepWarmResponse = async function(request, sleepTime = 0) 72 | { 73 | var coldStart = false; 74 | 75 | var now = moment(); 76 | 77 | if (warmTimestamp === undefined || functionId === undefined) 78 | { 79 | coldStart = true; 80 | warmTimestamp = now; 81 | functionId = uuidv4(); 82 | } 83 | else 84 | { 85 | if (sleepTime > 0) 86 | { 87 | console.info(`Sleeping for: ${sleepTime} millis`); 88 | await commonUtils.sleep(sleepTime); 89 | } 90 | } 91 | 92 | var response = { 93 | keepWarm: { 94 | id: functionId, 95 | name: request.keepWarm.name, 96 | arn: request.keepWarm.arn, 97 | coldStart: coldStart, 98 | runningTime: now.diff(warmTimestamp, 'seconds') 99 | } 100 | }; 101 | 102 | console.info(`Made keep warm response: ${JSON.stringify(response, null, 2)}`); 103 | return response; 104 | } 105 | -------------------------------------------------------------------------------- /lambda/utils/MockUtils.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var moment = require('moment'); 5 | 6 | const bots = 7 | [ 8 | { 9 | id: 'VYM6Z4CSUQ', 10 | name: 'TestBot', 11 | status: 'Available', 12 | description: 'A test bot', 13 | intents: [ 14 | { 15 | id: '9EAJSJFYKJ', 16 | name: 'Human', 17 | description: 'The user wants to speak to a person', 18 | utterances: [ 19 | 'Human', 20 | 'Help', 21 | 'Agent', 22 | 'Talk to a human' 23 | ], 24 | updated: moment().subtract(3, 'hours').format(), 25 | status: 'Available', 26 | deployed: true 27 | }, 28 | { 29 | id: 'IGMI1L3NZ3', 30 | name: 'Billing', 31 | utterances: [ 32 | ], 33 | updated: moment().subtract(2, 'hours').format(), 34 | deployed: true 35 | } 36 | ], 37 | deployed: true, 38 | updated: moment().subtract(2, 'hours').format() 39 | }, 40 | { 41 | id: 'e75d273a-2eea-4569-850e-f0f83ca38d66', 42 | name: 'FAQBot', 43 | description: 'A bot that powers FAQ matches', 44 | intents: [ 45 | { 46 | id: 'ada2ee95-c9ae-43cb-8e43-b2dafa81a41d', 47 | name: 'Human', 48 | utterances: [ 49 | 'Human', 50 | 'Help', 51 | 'Agent', 52 | 'Talk to a human' 53 | ], 54 | updated: moment().subtract(1, 'hours').format(), 55 | deployed: true 56 | }, 57 | { 58 | id: 'b53b92e3-8364-4520-885b-ef7b77955eeb', 59 | name: 'Payments', 60 | utterances: [ 61 | 'How do I pay' 62 | ], 63 | updated: moment().subtract(1, 'hours').format(), 64 | deployed: false 65 | } 66 | ], 67 | deployed: false, 68 | updated: moment().format() 69 | } 70 | ]; 71 | 72 | /** 73 | * Fetches all bots 74 | */ 75 | module.exports.getMockBots = () => 76 | { 77 | return bots; 78 | } 79 | 80 | /** 81 | * Fetches a mock bot by id 82 | */ 83 | module.exports.getMockBot = (botId) => 84 | { 85 | var bot = bots.find(bot => bot.id === botId); 86 | 87 | if (bot === null) 88 | { 89 | throw new Error('Failed to find bot with id: ' + botId); 90 | } 91 | 92 | return bot; 93 | } 94 | -------------------------------------------------------------------------------- /lambda/utils/PinpointUtils.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var AWS = require('aws-sdk'); 5 | AWS.config.update({region: process.env.REGION}); 6 | const pinpoint = new AWS.Pinpoint(); 7 | 8 | /** 9 | * Sends a message using pin point 10 | * messageType can PROMOTIONAL or TRANSACTIONAL 11 | */ 12 | module.exports.sendSMS = async function(phoneNumber, 13 | message, 14 | originationNumber, 15 | pinpointApplicationId, 16 | messageType = 'PROMOTIONAL') 17 | { 18 | try 19 | { 20 | 21 | var phoneNumberToUse = phoneNumber; 22 | 23 | // TODO assumes AU phone number here 24 | if (phoneNumberToUse.startsWith('0')) 25 | { 26 | phoneNumberToUse = '+61' + phoneNumberToUse.substring(1); 27 | } 28 | 29 | 30 | const params = 31 | { 32 | ApplicationId: pinpointApplicationId, 33 | MessageRequest: 34 | { 35 | Addresses: 36 | { 37 | [phoneNumberToUse]: 38 | { 39 | ChannelType: 'SMS', 40 | } 41 | }, 42 | MessageConfiguration: 43 | { 44 | SMSMessage: 45 | { 46 | Body: message, 47 | OriginationNumber: originationNumber, 48 | MessageType: messageType 49 | } 50 | } 51 | } 52 | }; 53 | 54 | console.info(`Sending Pinpoint SMS using: ${JSON.stringify(params, null, 2)}`); 55 | 56 | const result = await pinpoint.sendMessages(params).promise(); 57 | 58 | console.info(`Got Pinpoint result: ${JSON.stringify(result, null, 2)}`); 59 | 60 | if (result.MessageResponse.Result[phoneNumber].StatusCode !== 200) 61 | { 62 | console.error(`Pinpoint SMS message error code: ${result.MessageResponse.Result[phoneNumber].StatusCode}`); 63 | throw new Error(`Pinpoint SMS message error code: ${result.MessageResponse.Result[phoneNumber].StatusCode}`); 64 | } 65 | 66 | console.info(`Sent successful Pinpoint SMS message`); 67 | } 68 | catch (error) 69 | { 70 | console.error(`Detected error while sending Pinpoint SMS`, error); 71 | return error; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lambda/utils/PollyUtils.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var AWS = require('aws-sdk'); 5 | var polly = new AWS.Polly(); 6 | var moment = require('moment-timezone'); 7 | 8 | const { v4: uuidv4 } = require('uuid'); 9 | 10 | /** 11 | * Support mock injection 12 | */ 13 | module.exports.setDynamoDB = function(newPolly) 14 | { 15 | polly = newPolly; 16 | } 17 | 18 | /** 19 | * Renders speech from text using Amazon Polly 20 | * as a wav and returns this with Base64 encoding 21 | */ 22 | module.exports.synthesizeVoice = async (text, voiceId = 'Olivia', languageCode = 'en-AU') => 23 | { 24 | try 25 | { 26 | // TODO determine text type from input text, assumes 'text' for now 27 | 28 | var params = { 29 | OutputFormat: 'mp3', 30 | SampleRate: '8000', 31 | Text: text, 32 | Engine: 'neural', 33 | TextType: 'text', 34 | LanguageCode: languageCode, 35 | VoiceId: voiceId 36 | }; 37 | 38 | if (text.startsWith(' 11 | { 12 | try 13 | { 14 | const params = 15 | { 16 | Bucket: bucket, 17 | Key: key, 18 | Body: content 19 | }; 20 | await s3.putObject(params).promise(); 21 | } 22 | catch (error) 23 | { 24 | console.error('Failed to put object to S3', error); 25 | throw error; 26 | } 27 | }; 28 | 29 | /** 30 | * Fetches an object from S3 31 | */ 32 | module.exports.getObject = async (bucket, key) => 33 | { 34 | try 35 | { 36 | const params = 37 | { 38 | Bucket: bucket, 39 | Key: key 40 | }; 41 | 42 | var response = await s3.getObject(params).promise(); 43 | return response.Body.toString('utf-8'); 44 | } 45 | catch (error) 46 | { 47 | console.error('Failed to get object from S3', error); 48 | throw error; 49 | } 50 | }; 51 | 52 | /** 53 | * Fetches a presigned url for the requested object 54 | */ 55 | module.exports.getPresignedUrl = (bucket, key, expirySeconds = 5 * 60) => 56 | { 57 | return s3.getSignedUrl("getObject", { 58 | Bucket: bucket, 59 | Key: key, 60 | Expires: expirySeconds 61 | }); 62 | }; 63 | 64 | -------------------------------------------------------------------------------- /lambda/utils/SNSUtils.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var AWS = require('aws-sdk'); 5 | AWS.config.update({region: process.env.REGION}); 6 | var sns = new AWS.SNS(); 7 | 8 | /** 9 | * Sends an SMS message via SNS 10 | */ 11 | module.exports.sendSMS = async function(phoneNumber, message) 12 | { 13 | try 14 | { 15 | var phoneNumberToUse = phoneNumber; 16 | 17 | // TODO assumes AU phone number here 18 | if (phoneNumberToUse.startsWith('0')) 19 | { 20 | phoneNumberToUse = '+61' + phoneNumberToUse.substring(1); 21 | } 22 | 23 | var params = { 24 | Message: message, 25 | PhoneNumber: phoneNumberToUse 26 | }; 27 | 28 | console.log('[INFO] making SMS request: ' + JSON.stringify(params, null, ' ')); 29 | 30 | await sns.publish(params).promise(); 31 | } 32 | catch (error) 33 | { 34 | console.log('[ERROR] failed to send SMS', error); 35 | throw error; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lex/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /lex/bots/date.json: -------------------------------------------------------------------------------- 1 | { 2 | "format": "2", 3 | "name": "date", 4 | "description": "Fetches a date from the customer", 5 | "localeId": "en_AU", 6 | "voice": "Olivia", 7 | "engine": "neural", 8 | "productionAlias": "PROD", 9 | "challengerAlias": "CHALLENGER", 10 | "detectSentiment": false, 11 | "idleSessionTTLInSeconds": 60, 12 | "confidenceThreshold": 0.4, 13 | "fulfillmentFunction": "lexfulfillment", 14 | "slots": 15 | [ 16 | { 17 | "name": "dataslot", 18 | "description": "An input date slot", 19 | "parentType": "AMAZON.Date", 20 | "prompt": "Please tell me the date?", 21 | "required": false 22 | } 23 | ], 24 | "intents": [ 25 | { 26 | "name": "nodata", 27 | "description": "Customer doesn't have a date", 28 | "utterances": [ 29 | "I don't have it", 30 | "I don't have one", 31 | "Don't know the date", 32 | "Don't know the date of birth", 33 | "No date", 34 | "Not available", 35 | "Don't know it", 36 | "Don't have it", 37 | "I don't know that", 38 | "Don't know", 39 | "Don't know that", 40 | "Unknown", 41 | "No thanks", 42 | "Who knows?", 43 | "None", 44 | "No", 45 | "Nah", 46 | "Dunno", 47 | "Umm", 48 | "Err", 49 | "Yeah", 50 | "Well", 51 | "Beats me", 52 | "#", 53 | "hash" 54 | ], 55 | "tests": 56 | [ 57 | { 58 | "utterance": "I don't know her date of birth", 59 | "confidence": 0.82 60 | }, 61 | { 62 | "utterance": "I can't recall it sorry", 63 | "confidence": 0.8 64 | }, 65 | { 66 | "utterance": "No", 67 | "confidence": 0.8 68 | }, 69 | { 70 | "utterance": "I don't know it mate", 71 | "confidence": 0.8 72 | }, 73 | { 74 | "utterance": "Nahh", 75 | "confidence": 0.83 76 | }, 77 | { 78 | "utterance": "No thanks mate", 79 | "confidence": 0.83 80 | }, 81 | { 82 | "utterance": "hash", 83 | "confidence": 1.0 84 | }, 85 | { 86 | "utterance": "#", 87 | "confidence": 1.0 88 | } 89 | ] 90 | }, 91 | { 92 | "name": "intentdata", 93 | "description": "An input date", 94 | "utterances": [ 95 | "{dataslot}" 96 | ], 97 | "tests": 98 | [ 99 | { 100 | "utterance": "23rd of July 2001", 101 | "confidence": 1.0, 102 | "slots": [ 103 | { 104 | "name": "dataslot", 105 | "value": "2001-07-23" 106 | } 107 | ] 108 | }, 109 | { 110 | "utterance": "15 Dec 2023", 111 | "confidence": 1.0, 112 | "slots": [ 113 | { 114 | "name": "dataslot", 115 | "value": "2023-12-15" 116 | } 117 | ] 118 | } 119 | ] 120 | } 121 | ] 122 | } 123 | -------------------------------------------------------------------------------- /lex/bots/number.json: -------------------------------------------------------------------------------- 1 | { 2 | "format": "2", 3 | "name": "number", 4 | "description": "Fetches a number from the customer", 5 | "localeId": "en_AU", 6 | "voice": "Olivia", 7 | "engine": "neural", 8 | "productionAlias": "PROD", 9 | "challengerAlias": "CHALLENGER", 10 | "detectSentiment": false, 11 | "idleSessionTTLInSeconds": 60, 12 | "confidenceThreshold": 0.4, 13 | "fulfillmentFunction": "lexfulfillment", 14 | "slots": 15 | [ 16 | { 17 | "name": "dataslot", 18 | "description": "An input number slot", 19 | "parentType": "AMAZON.Number", 20 | "prompt": "Please tell the number?", 21 | "required": false 22 | } 23 | ], 24 | "intents": [ 25 | { 26 | "name": "nodata", 27 | "description": "Customer doesn't have a number", 28 | "utterances": [ 29 | "I don't have it", 30 | "I don't have one", 31 | "Don't know the number", 32 | "Don't know it", 33 | "I don't know the post code", 34 | "I don't have my account number", 35 | "I don't know how many", 36 | "Don't have it", 37 | "I don't know that", 38 | "Don't know", 39 | "Don't know that", 40 | "No number", 41 | "No post code", 42 | "No account number", 43 | "None", 44 | "No", 45 | "Dunno", 46 | "Umm", 47 | "Err", 48 | "Yeah", 49 | "Nah", 50 | "Well", 51 | "Beats me", 52 | "#", 53 | "hash" 54 | ], 55 | "tests": 56 | [ 57 | { 58 | "utterance": "I no have a post code", 59 | "confidence": 0.82 60 | }, 61 | { 62 | "utterance": "No post code", 63 | "confidence": 0.8 64 | }, 65 | { 66 | "utterance": "No", 67 | "confidence": 0.8 68 | }, 69 | { 70 | "utterance": "I don't know the number sorry", 71 | "confidence": 0.8 72 | }, 73 | { 74 | "utterance": "Nahh", 75 | "confidence": 0.83 76 | }, 77 | { 78 | "utterance": "hash", 79 | "confidence": 1.0 80 | }, 81 | { 82 | "utterance": "#", 83 | "confidence": 1.0 84 | } 85 | ] 86 | }, 87 | { 88 | "name": "intentdata", 89 | "description": "An input number", 90 | "utterances": [ 91 | "{dataslot}" 92 | ], 93 | "tests": 94 | [ 95 | { 96 | "utterance": "4566", 97 | "confidence": 1.0, 98 | "slots": [ 99 | { 100 | "name": "dataslot", 101 | "value": "4566" 102 | } 103 | ] 104 | } 105 | ] 106 | } 107 | ] 108 | } 109 | -------------------------------------------------------------------------------- /lex/bots/yesno.json: -------------------------------------------------------------------------------- 1 | { 2 | "format": "2", 3 | "name": "yesno", 4 | "description": "Fetches a yes or not response from the customer", 5 | "localeId": "en_AU", 6 | "voice": "Olivia", 7 | "engine": "neural", 8 | "productionAlias": "PROD", 9 | "challengerAlias": "CHALLENGER", 10 | "detectSentiment": false, 11 | "idleSessionTTLInSeconds": 60, 12 | "confidenceThreshold": 0.4, 13 | "fulfillmentFunction": "lexfulfillment", 14 | "slots": [], 15 | "intents": [ 16 | { 17 | "name": "Yes", 18 | "description": "Customer says yes", 19 | "utterances": [ 20 | "1", 21 | "Yeah", 22 | "Yes", 23 | "Yeah that's right", 24 | "Yeah mate", 25 | "Yeah right", 26 | "Yep", 27 | "OK", 28 | "Ya", 29 | "One", 30 | "Good", 31 | "Proceed", 32 | "Positive", 33 | "Correct", 34 | "That is correct", 35 | "Indeed" 36 | ], 37 | "tests": 38 | [ 39 | { 40 | "utterance": "Yep", 41 | "confidence": 0.9 42 | }, 43 | { 44 | "utterance": "Yes", 45 | "confidence": 0.9 46 | }, 47 | { 48 | "utterance": "1", 49 | "confidence": 0.9 50 | }, 51 | { 52 | "utterance": "Good", 53 | "confidence": 0.9 54 | }, 55 | { 56 | "utterance": "Yeah mate", 57 | "confidence": 0.9 58 | }, 59 | { 60 | "utterance": "One", 61 | "confidence": 0.9 62 | }, 63 | { 64 | "utterance": "That is correct", 65 | "confidence": 0.9 66 | } 67 | ] 68 | }, 69 | { 70 | "name": "No", 71 | "description": "Customer says no", 72 | "utterances": [ 73 | "No", 74 | "2", 75 | "Bad", 76 | "Two", 77 | "Wrong", 78 | "Nah", 79 | "Nup", 80 | "Umm", 81 | "Err", 82 | "What", 83 | "Nope", 84 | "Negative", 85 | "Negatory", 86 | "Incorrect", 87 | "Not correct", 88 | "That is not correct" 89 | ], 90 | "tests": 91 | [ 92 | { 93 | "utterance": "No that is not correct", 94 | "confidence": 0.8 95 | }, 96 | { 97 | "utterance": "Nah", 98 | "confidence": 0.9 99 | }, 100 | { 101 | "utterance": "Nup", 102 | "confidence": 0.9 103 | }, 104 | { 105 | "utterance": "No", 106 | "confidence": 0.9 107 | }, 108 | { 109 | "utterance": "2", 110 | "confidence": 0.9 111 | }, 112 | { 113 | "utterance": "Two", 114 | "confidence": 0.9 115 | }, 116 | { 117 | "utterance": "Umm err", 118 | "confidence": 0.8 119 | }, 120 | { 121 | "utterance": "Nope mate", 122 | "confidence": 0.8 123 | }, 124 | { 125 | "utterance": "Negatory dude", 126 | "confidence": 0.8 127 | } 128 | ] 129 | } 130 | ] 131 | } 132 | -------------------------------------------------------------------------------- /lex/build_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | failuresDetected=0 6 | 7 | recordError() 8 | { 9 | echo "[ERROR] detected a failure building bot: $1, continuing" 10 | failuresDetected=$((failuresDetected+1)) 11 | } 12 | 13 | # Load the shared environment 14 | source ../env/$1.sh 15 | 16 | ../scripts/rules_engine_check_aws_account.sh 17 | 18 | npm install 19 | 20 | echo "[INFO] Checking service linked role exists for Lex, an error will be printed if this exists" 21 | aws iam create-service-linked-role --aws-service-name lex.amazonaws.com || true 22 | 23 | for i in ./bots/*.json; do 24 | echo "[INFO] building bot: $i" 25 | node deploy_lex_bot.js $i $2 || recordError $i 26 | done 27 | 28 | if [[ "$failuresDetected" -gt 0 ]]; then 29 | echo "[ERROR] recorded $failuresDetected failures!" 30 | exit 1 31 | fi 32 | -------------------------------------------------------------------------------- /lex/build_one.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Load the shared environment 6 | source ../env/$2.sh 7 | 8 | ../scripts/rules_engine_check_aws_account.sh 9 | 10 | npm install 11 | 12 | echo "[INFO] building bot: $1" 13 | node deploy_lex_bot.js $1 $3 14 | -------------------------------------------------------------------------------- /lex/delete_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | failuresDetected=0 6 | 7 | recordError() 8 | { 9 | echo "[ERROR] detected a failure deleting bot: $1, continuing" 10 | failuresDetected=$((failuresDetected+1)) 11 | } 12 | 13 | # Load the shared environment 14 | source ../env/$1.sh 15 | 16 | ../scripts/rules_engine_check_aws_account.sh 17 | 18 | npm install 19 | 20 | for i in ./bots/*.json; do 21 | echo "[INFO] deleting bot: $i" 22 | node delete_lex_bot.js $i || recordError $i 23 | done 24 | 25 | if [[ "$failuresDetected" -gt 0 ]]; then 26 | echo "[ERROR] recorded $failuresDetected failures!" 27 | exit 1 28 | fi 29 | -------------------------------------------------------------------------------- /lex/delete_one.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Load the shared environment 6 | source ../env/$2.sh 7 | 8 | ../scripts/rules_engine_check_aws_account.sh 9 | 10 | npm install 11 | 12 | echo "[INFO] deleting bot: $1" 13 | node delete_lex_bot.js $1 14 | -------------------------------------------------------------------------------- /lex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lex-builder", 3 | "version": "1.0.0", 4 | "description": "Builds lex bots in an environment", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "jospas@amazon.com", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "aws-sdk": "^2.1185.0", 13 | "uuid": "^8.3.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /manual/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/admin.png -------------------------------------------------------------------------------- /manual/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/architecture.png -------------------------------------------------------------------------------- /manual/error_weight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/error_weight.png -------------------------------------------------------------------------------- /manual/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/graph.png -------------------------------------------------------------------------------- /manual/graph_filtered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/graph_filtered.png -------------------------------------------------------------------------------- /manual/health_loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/health_loading.png -------------------------------------------------------------------------------- /manual/healthy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/healthy.png -------------------------------------------------------------------------------- /manual/holidays.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/holidays.png -------------------------------------------------------------------------------- /manual/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/home.png -------------------------------------------------------------------------------- /manual/home_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/home_login.png -------------------------------------------------------------------------------- /manual/integration_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/integration_architecture.png -------------------------------------------------------------------------------- /manual/integration_rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/integration_rule.png -------------------------------------------------------------------------------- /manual/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/loading.png -------------------------------------------------------------------------------- /manual/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/login.png -------------------------------------------------------------------------------- /manual/pipeline-setup1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/pipeline-setup1.png -------------------------------------------------------------------------------- /manual/pipeline-setup2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/pipeline-setup2.png -------------------------------------------------------------------------------- /manual/pipeline-setup2a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/pipeline-setup2a.png -------------------------------------------------------------------------------- /manual/pipeline-setup2b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/pipeline-setup2b.png -------------------------------------------------------------------------------- /manual/pipeline-setup3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/pipeline-setup3.png -------------------------------------------------------------------------------- /manual/pipeline-setup3a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/pipeline-setup3a.png -------------------------------------------------------------------------------- /manual/pipeline-setup3b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/pipeline-setup3b.png -------------------------------------------------------------------------------- /manual/repair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/repair.png -------------------------------------------------------------------------------- /manual/repaired.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/repaired.png -------------------------------------------------------------------------------- /manual/repairing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/repairing.png -------------------------------------------------------------------------------- /manual/rule_weights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/rule_weights.png -------------------------------------------------------------------------------- /manual/rulesets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/rulesets.png -------------------------------------------------------------------------------- /manual/training_installation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/training_installation.png -------------------------------------------------------------------------------- /manual/training_lex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/training_lex.png -------------------------------------------------------------------------------- /manual/training_weights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/manual/training_weights.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rulesengine", 3 | "version": "3.0.0", 4 | "description": "A rules engine or Amazon Connect", 5 | "author": "jospas@amazon.com", 6 | "license": "Apache-2.0", 7 | "dependencies": { 8 | "axios": "^0.27.2", 9 | "handlebars": "^4.7.7", 10 | "lru-cache": "^7.13.1", 11 | "moment": "^2.29.4", 12 | "moment-timezone": "^0.5.35", 13 | "node-gzip": "^1.1.2", 14 | "papaparse": "^5.3.2", 15 | "sprintf-js": "^1.1.2", 16 | "uuid": "^8.3.2", 17 | "weighted": "^0.3.0" 18 | }, 19 | "devDependencies": { 20 | "aws-sdk-mock": "^5.7.0", 21 | "chai": "^4.3.6", 22 | "mocha": "^10.0.0", 23 | "nyc": "^15.1.0", 24 | "rewire": "^6.0.0", 25 | "serverless": "^3.21.0", 26 | "serverless-prune-plugin": "^2.0.1", 27 | "sinon": "^14.0.0" 28 | }, 29 | "scripts": { 30 | "test": "npx nyc mocha --recursive ./test" 31 | }, 32 | "engines": { 33 | "node": ">=20.x" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /scripts/rules_engine_check_aws_account.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | date 6 | 7 | echo "[INFO] Checking AWS account number matches configured environment..." 8 | localAccountNumber=$(aws sts get-caller-identity --query "Account" --output text) 9 | if [ "$localAccountNumber" == "$accountNumber" ]; then 10 | echo "[INFO] Verified deployment AWS account number matches credentials, proceeding!" 11 | exit 0 12 | else 13 | echo "[ERROR] Found mismatched AWS account number in credentials, was expecting: ${accountNumber} found: ${localAccountNumber} check credentials!" 14 | exit 1 15 | fi 16 | -------------------------------------------------------------------------------- /scripts/rules_engine_create_deployment_bucket.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | date 6 | 7 | source ./env/$1.sh 8 | 9 | echo "Creating deployment bucket for $1" 10 | 11 | ./scripts/rules_engine_check_aws_account.sh 12 | 13 | aws s3 mb \ 14 | --region ${region} \ 15 | s3://${stage}-${service}-deployment-${accountNumber} 16 | -------------------------------------------------------------------------------- /scripts/rules_engine_extract_flows.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | date 4 | 5 | source ./env/$1.sh 6 | 7 | echo "Extracting contact flows for $environmentName" 8 | 9 | ./scripts/rules_engine_check_aws_account.sh 10 | 11 | mkdir -p ./temp 12 | 13 | echo "Listing contact flows" 14 | aws connect list-contact-flows \ 15 | --instance-id ${instanceId} \ 16 | --region ${region} \ 17 | --contact-flow-types 'CONTACT_FLOW' 'AGENT_WHISPER' 'CUSTOMER_HOLD' 'CUSTOMER_QUEUE' 'CUSTOMER_WHISPER' 'OUTBOUND_WHISPER' > ./temp/contact_flows.json 18 | 19 | echo "Preparing contact flow fix data" 20 | node ./scripts/rules_engine_prepare_fixes.js ./temp/fixes.json 21 | 22 | process_contactflow () { 23 | 24 | contactFlowName=$1 25 | echo ------------------------------------------ 26 | echo "Processing contact flow: $contactFlowName" 27 | 28 | rawOutputFile1=./temp/${contactFlowName}_raw1.json 29 | rawOutputFile2=./temp/${contactFlowName}_raw2.json 30 | finalOutputFile=./connect/contactflows/${contactFlowName}.json 31 | contactFlowId=$(cat ./temp/contact_flows.json | jq -r ".ContactFlowSummaryList | map(select(.Name == (\"${contactFlowName}\")).Id) | .[]" ) 32 | 33 | aws connect describe-contact-flow \ 34 | --instance-id $instanceId \ 35 | --region $region \ 36 | --contact-flow-id ${contactFlowId} > ${rawOutputFile1} 37 | 38 | cat ${rawOutputFile1} | jq -r ".ContactFlow.Content" > ${rawOutputFile2} 39 | 40 | cat ${rawOutputFile2} | jq -r "" > ${finalOutputFile} 41 | 42 | node ./scripts/rules_engine_filter_flows.js ./temp/fixes.json ${finalOutputFile} 43 | 44 | echo "Processed contact flow: $contactFlowName" 45 | } 46 | 47 | # process_contactflow "empty_customer_whisper_flow" 48 | # process_contactflow "empty_agent_whisper_flow" 49 | # process_contactflow "empty_customer_hold_flow" 50 | # process_contactflow "empty_customer_queue_flow" 51 | # process_contactflow "empty_outbound_whisper_flow" 52 | process_contactflow "RulesEngineAgentWhisper" 53 | process_contactflow "RulesEngineCustomerHold" 54 | process_contactflow "RulesEngineCustomerQueue" 55 | process_contactflow "RulesEngineCustomerWhisper" 56 | process_contactflow "RulesEngineOutboundWhisper" 57 | process_contactflow "RulesEngineBootstrap" 58 | process_contactflow "RulesEngineMain" 59 | process_contactflow "RulesEngineDisconnect" 60 | process_contactflow "RulesEngineError" 61 | process_contactflow "RulesEngineDTMFMenu" 62 | process_contactflow "RulesEngineDTMFInput" 63 | process_contactflow "RulesEngineExternalNumber" 64 | process_contactflow "RulesEngineIntegration" 65 | process_contactflow "RulesEngineMessage" 66 | process_contactflow "RulesEngineNLUInput" 67 | process_contactflow "RulesEngineNLUMenu" 68 | process_contactflow "RulesEngineQueue" 69 | process_contactflow "RulesEngineRuleSet" 70 | process_contactflow "RulesEngineSMSMessage" 71 | process_contactflow "RulesEngineTerminate" 72 | process_contactflow "RulesEngineWait" 73 | -------------------------------------------------------------------------------- /scripts/rules_engine_filter_flows.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | 4 | /** 5 | * Filter contact flows with the provided fix file 6 | */ 7 | function filterContactFlow(fixFile, fileName) 8 | { 9 | try 10 | { 11 | var fixData = JSON.parse(fs.readFileSync(fixFile, 'UTF-8')); 12 | var rawContactFlow = fs.readFileSync(fileName, 'UTF-8'); 13 | 14 | fixData.forEach(fix => 15 | { 16 | while (rawContactFlow.includes(fix.original)) 17 | { 18 | rawContactFlow = rawContactFlow.replace(fix.original, fix.replacement); 19 | } 20 | }); 21 | 22 | if (rawContactFlow.includes('arn:')) 23 | { 24 | console.error(`Failed to template contact flow, found untemplated arn in: ${fileName}\n${rawContactFlow}`); 25 | throw new Error('Failed to template contact flow, found untemplated arn in: ' + fileName); 26 | 27 | } 28 | 29 | fs.writeFileSync(fileName, rawContactFlow); 30 | } 31 | catch (error) 32 | { 33 | console.log('[ERROR] failed process contact flow: ' + fileName, error); 34 | throw error; 35 | } 36 | } 37 | 38 | filterContactFlow(process.argv[2], process.argv[3]); 39 | -------------------------------------------------------------------------------- /scripts/rules_engine_function_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | date 6 | 7 | source ./env/$2.sh 8 | 9 | echo "Deployingfunction $1 to $2" 10 | 11 | ./scripts/rules_engine_check_aws_account.sh 12 | 13 | # Run unit tests 14 | npm test 15 | echo 'Unit tests passed' 16 | 17 | # TODO renable tests 18 | # echo 'Skipping unit tests' 19 | 20 | serverless deploy function -f "$1" --force 21 | -------------------------------------------------------------------------------- /scripts/rules_engine_s3_clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | date 6 | 7 | source ./env/$1.sh 8 | 9 | echo "Cleaning Rules engine S3 for $1" 10 | 11 | ./scripts/rules_engine_check_aws_account.sh 12 | 13 | s3Bucket="s3://${stage}-${service}-site-${region}-${accountNumber}/" 14 | 15 | aws s3 rm --recursive $s3Bucket 16 | 17 | -------------------------------------------------------------------------------- /scripts/rules_engine_s3_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | date 6 | 7 | source ./env/$1.sh 8 | 9 | echo "Deploying Rules engine to S3 for $1" 10 | 11 | ./scripts/rules_engine_check_aws_account.sh 12 | 13 | s3Bucket="s3://${stage}-${service}-site-${region}-${accountNumber}/" 14 | 15 | echo "Describing the stack and building web config" 16 | 17 | aws cloudformation --region "${region}" describe-stacks \ 18 | --stack-name "${stage}-${service}" \ 19 | --query "Stacks[0].Outputs[?OutputKey=='SiteConfigTemplate'].OutputValue" \ 20 | --output text > web/config/site_config.json 21 | 22 | cd web/ 23 | 24 | echo "Deploying web assets to bucket: $s3Bucket" 25 | 26 | aws s3 cp --recursive \ 27 | --cache-control 'no-cache' \ 28 | --exclude "*.DS_Store" \ 29 | --exclude "Desktop.ini" \ 30 | --exclude "desktop.ini" \ 31 | --exclude "*.git" \ 32 | --exclude "*.gitignore" \ 33 | . $s3Bucket 34 | 35 | echo "S3 deployment complete, site URL: " 36 | 37 | aws cloudformation --region "${region}" describe-stacks \ 38 | --stack-name "${stage}-${service}" \ 39 | --query "Stacks[0].Outputs[?OutputKey=='WebsiteURL'].OutputValue" \ 40 | --output text 41 | -------------------------------------------------------------------------------- /scripts/rules_engine_serverless_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | date 6 | 7 | source ./env/$1.sh 8 | 9 | echo "Deploying Rules engine to $environmentName" 10 | 11 | ./scripts/rules_engine_check_aws_account.sh 12 | 13 | # Install serverless globally if required 14 | #npm install -g serverless 15 | 16 | # Install required packages 17 | npm install 18 | 19 | # Run unit tests 20 | npm test 21 | 22 | echo 'Unit tests passed' 23 | 24 | echo 'Commencing full forced deploy, grab a cup of coffee, takes several minutes' 25 | 26 | serverless deploy --force 27 | 28 | echo "Inserting admin user into DynamoDB" 29 | 30 | aws dynamodb put-item \ 31 | --region "${region}" \ 32 | --table-name "${stage}-${service}-users-ddb" \ 33 | --item "file://data/users/${stage}-admin.json" 34 | 35 | echo "Inserting timezone config data into DynamoDB" 36 | 37 | aws dynamodb put-item \ 38 | --region "${region}" \ 39 | --table-name "${stage}-${service}-config-ddb" \ 40 | --item file://data/timezone.json 41 | 42 | if [ -f "data/keepwarm/${stage}-keepwarm.json" ]; then 43 | echo "Inserting keep warm config into DynamoDB" 44 | aws dynamodb put-item \ 45 | --region "${region}" \ 46 | --table-name "${stage}-${service}-config-ddb" \ 47 | --item "file://data/keepwarm/${stage}-keepwarm.json" 48 | else 49 | echo "Skipping inserting keep warm configuration due to missing file: data/keepwarm/${stage}-keepwarm.json" 50 | fi 51 | 52 | ./scripts/rules_engine_s3_deploy.sh $1 53 | -------------------------------------------------------------------------------- /test/BackoffUtilsTests.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var rewire = require('rewire'); 5 | const expect = require('chai').expect; 6 | const config = require('./utils/config.js'); 7 | const backoffUtils = rewire('../lambda/utils/BackoffUtils.js'); 8 | 9 | var computeSleepTime = backoffUtils.__get__('computeSleepTime') 10 | 11 | describe('BackoffUtilsTests', function() { 12 | this.timeout(60000); 13 | 14 | this.beforeAll(function () { 15 | config.loadEnv(); 16 | }); 17 | 18 | // Test computing sleep times to be within expected bounds 19 | it('computeSleepTime() should be bounded based on retry', function() { 20 | 21 | var maxTimes = [ 22 | 250, 23 | 500, 24 | 1000 25 | ]; 26 | 27 | for (var i = 0; i < maxTimes.length; i++) 28 | { 29 | var sleep = computeSleepTime(i); 30 | expect(sleep).to.be.within(250, maxTimes[i]); 31 | } 32 | }); 33 | 34 | // Tests actually backing off and sleeping 35 | it('backoff() should sleep', async function() { 36 | 37 | console.info('Testing backoff for various retry counts:'); 38 | 39 | var error = new Error('Something on fire?'); 40 | 41 | for (var i = 0; i < 3; i++) 42 | { 43 | try 44 | { 45 | await backoffUtils.backoff(`Retry: ${i}`, i, error); 46 | } 47 | catch (error) 48 | { 49 | throw new Error ("backoffUtils.backoff() should not fail", error); 50 | } 51 | } 52 | }); 53 | 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /test/BatchInferenceRunnerTest.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const expect = require('chai').expect; 5 | 6 | /** 7 | * Tests batch size logic 8 | */ 9 | describe('BatchInferenceRunnerTest', function() 10 | { 11 | 12 | it('Test batch size logic', async function() { 13 | 14 | var batchSize = 3; 15 | var testIds = [ 16 | '1', '2', '3', '4', '5', '6', '7', '8' 17 | ]; 18 | 19 | var testResults = []; 20 | 21 | var startIndex = 0; 22 | 23 | var batchCount = 0; 24 | 25 | while (startIndex < testIds.length) 26 | { 27 | var endIndex = Math.min(testIds.length, startIndex + batchSize); 28 | 29 | var batchTestIds = testIds.slice(startIndex, endIndex); 30 | 31 | console.info('Got batch test ids: ' + JSON.stringify(batchTestIds, null, 2)); 32 | 33 | // Start executing each test and keep the promises aside 34 | for (var i = 0; i < batchTestIds.length; i++) 35 | { 36 | var testId = batchTestIds[i]; 37 | 38 | testResults.push(testId); 39 | } 40 | 41 | batchCount++; 42 | startIndex += batchSize; 43 | } 44 | 45 | expect(testResults.length).to.equal(testIds.length); 46 | expect(batchCount).to.equal(3); 47 | }); 48 | 49 | }); 50 | 51 | -------------------------------------------------------------------------------- /test/ConnectUtilsTest.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const expect = require('chai').expect; 5 | const config = require('./utils/config'); 6 | const connectUtils = require('../lambda/utils/ConnectUtils'); 7 | 8 | describe('PromptsMapTest', function() { 9 | this.beforeAll(function () { 10 | config.loadEnv(); 11 | }); 12 | 13 | it('Should escape all non-alpha numeric with underscore', async function() { 14 | var promptsList = [ 15 | { 16 | Name: 'Simple-Wav%20;With_Spaces&Stuff (Nuts).wav', 17 | Id: 'eb6a445c-d707-11ec-94ff-f731421e444e', 18 | Arn: 'Some arn' 19 | } 20 | ]; 21 | 22 | var promptsMap = connectUtils.getPromptsMap(promptsList); 23 | 24 | console.info(JSON.stringify(promptsMap, null, 2)); 25 | 26 | expect(promptsMap['Simple_Wav_20_With_Spaces_Stuff__Nuts__wav'].id).to.equal('eb6a445c-d707-11ec-94ff-f731421e444e'); 27 | expect(promptsMap['Simple_Wav_20_With_Spaces_Stuff__Nuts__wav'].arn).to.equal('Some arn'); 28 | }); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /test/KeepWarmUtilsTest.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const expect = require('chai').expect; 5 | const keepWarmUtils = require('../lambda/utils/KeepWarmUtils'); 6 | 7 | /** 8 | * KeepWarmUtils tests 9 | */ 10 | describe('KeepWarmUtilsTests', function() 11 | { 12 | this.beforeAll(function() 13 | { 14 | keepWarmUtils.reset(); 15 | }); 16 | 17 | this.afterAll(function() 18 | { 19 | keepWarmUtils.reset(); 20 | }); 21 | 22 | // Tests creating a request 23 | it('KeepWarmUtils.createKeepWarmRequest()', async function() 24 | { 25 | var request = keepWarmUtils.createKeepWarmRequest('functionName', 'functionArn'); 26 | 27 | var expected = { 28 | keepWarm: { 29 | name: 'functionName', 30 | arn: 'functionArn', 31 | } 32 | }; 33 | 34 | expect(JSON.stringify(request)).to.equal(JSON.stringify(expected)); 35 | }); 36 | 37 | it('keepWarmUtils.isKeepWarmRequest()', async function() 38 | { 39 | var request = keepWarmUtils.createKeepWarmRequest('functionName', 'functionArn'); 40 | 41 | expect(keepWarmUtils.isKeepWarmRequest(request)).to.equal(true); 42 | 43 | request = {}; 44 | 45 | expect(keepWarmUtils.isKeepWarmRequest(request)).to.equal(false); 46 | }); 47 | 48 | // Tests making a response 49 | it('KeepWarmUtils.makeKeepWarmResponse()', async function() 50 | { 51 | var request = keepWarmUtils.createKeepWarmRequest('functionName', 'functionArn'); 52 | var response = await keepWarmUtils.makeKeepWarmResponse(request); 53 | expect(keepWarmUtils.isKeepWarmResponse(response)).to.equal(true); 54 | expect(response.keepWarm.coldStart).to.equal(true); 55 | 56 | response = await keepWarmUtils.makeKeepWarmResponse(request, 100); 57 | expect(keepWarmUtils.isKeepWarmResponse(response)).to.equal(true); 58 | expect(response.keepWarm.coldStart).to.equal(false); 59 | 60 | response = await keepWarmUtils.makeKeepWarmResponse(request, 0); 61 | expect(keepWarmUtils.isKeepWarmResponse(response)).to.equal(true); 62 | expect(response.keepWarm.coldStart).to.equal(false); 63 | 64 | expect(keepWarmUtils.isKeepWarmResponse({})).to.equal(false); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/LexUtilsTest.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const expect = require('chai').expect; 5 | var lexUtils = require('../lambda/utils/LexUtils'); 6 | 7 | 8 | /** 9 | * InferenceUtils tests 10 | */ 11 | describe('InferenceUtilsTests', function() 12 | { 13 | this.beforeAll(function() 14 | { 15 | }); 16 | 17 | this.afterAll(function() 18 | { 19 | }); 20 | 21 | // Tests expanding a raw input transcript containing double and triple 22 | it('LexUtils.expandPhoneNumber()', async function() 23 | { 24 | var testData = [ 25 | { 26 | input: '', 27 | output: undefined 28 | }, 29 | { 30 | input: null, 31 | output: undefined 32 | }, 33 | { 34 | input: undefined, 35 | output: undefined 36 | }, 37 | { 38 | input: '0422529062', 39 | output: '0422529062' 40 | }, 41 | { 42 | input: 'oh Four double 2 5 two nine zero six two', 43 | output: '0422529062' 44 | }, 45 | { 46 | input: 'The lazy brown dog jumped over the big red hen', 47 | output: 'The lazy brown dog jumped over the big red hen' 48 | }, 49 | { 50 | input: 'Zero 4 double two three seven triple five', 51 | output: '042237555' 52 | }, 53 | { 54 | input: 'Zero 4 double two test seven triple five', 55 | output: 'Zero 4 double two test seven triple five' 56 | }, 57 | { 58 | input: 'Plus sixty one double two seven triple five double eight', 59 | output: '+6122755588' 60 | }, 61 | { 62 | input: 'Plus sixty seven three thousand double five triple two', 63 | output: '+67300055222' 64 | } 65 | , 66 | { 67 | input: 'oh four double double one um three six err two four triple double four', 68 | output: '0411362444' 69 | } 70 | ]; 71 | 72 | 73 | for (var i = 0; i < testData.length; i++) 74 | { 75 | expect(lexUtils.expandPhoneNumber(testData[i].input)).to.equal(testData[i].output); 76 | } 77 | }); 78 | 79 | }); 80 | 81 | -------------------------------------------------------------------------------- /test/VerifyLoginTests.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const expect = require("chai").expect; 5 | const AWSMock = require('aws-sdk-mock'); 6 | const config = require('./utils/config.js'); 7 | const lambda = require('../lambda/VerifyLogin.js').handler; 8 | const dynamoUtils = require('../lambda/utils/DynamoUtils.js'); 9 | const dynamoTableMocker = require('./utils/DynamoTableDataMocker.js'); 10 | const mockEventGenerator = require('./utils/MockEventGenerator.js'); 11 | 12 | describe('VerifyLoginTests', function() { 13 | this.beforeAll(function () { 14 | config.loadEnv(); 15 | dynamoTableMocker.setupMockDynamo(AWSMock, dynamoUtils); 16 | }); 17 | 18 | it('should accept a valid api key', async function() { 19 | var event = mockEventGenerator.generateVerifyLoginMockdata("1a123456-ab12-1a2a-1c23-b123456ef789"); 20 | var data = await lambda(event, null, function (err, data) {if (err) throw err; else return data;}); 21 | var body = JSON.parse(data.body); 22 | expect(body.user.userId).to.equal("19b1dc36-3ad6-11ec-97b9-b124ea8f11eb"); 23 | expect(data.statusCode).to.equal(200); 24 | }); 25 | 26 | it('should not accept an invalid api key', async function() { 27 | var event = mockEventGenerator.generateVerifyLoginMockdata("1a222222-ff11-1e1a-1c11-b111111ef111"); 28 | var data = await lambda(event, null, function (err, data) {if (err) throw err; else return data;}); 29 | var body = JSON.parse(data.body); 30 | expect(body.user).to.equal(undefined); 31 | expect(data.statusCode).to.equal(401); 32 | }); 33 | 34 | this.afterAll(function () { 35 | AWSMock.restore('DynamoDB'); 36 | }) 37 | }); 38 | -------------------------------------------------------------------------------- /test/interactive/InteractiveConfig.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | "use strict"; 5 | 6 | const loadEnv = function () { 7 | process.env['VALID_ORIGINS'] = '["https://unit-testing-aws.cloudfront.net"]'; 8 | process.env['USERS_TABLE'] = `${process.env.stage}-${process.env.service}-users-ddb`; 9 | process.env['RULE_SETS_TABLE'] = `${process.env.stage}-${process.env.service}-rule-sets-ddb`; 10 | process.env['RULES_TABLE'] = `${process.env.stage}-${process.env.service}-rules-ddb`; 11 | process.env['CONFIG_TABLE'] = `${process.env.stage}-${process.env.service}-config-ddb`; 12 | process.env['STATE_TABLE'] = `${process.env.stage}-${process.env.service}-state-ddb`; 13 | process.env['TESTS_TABLE'] = `${process.env.stage}-${process.env.service}-tests-ddb`; 14 | process.env['CALLBACK_TABLE'] = `${process.env.stage}-${process.env.service}-callback-ddb`; 15 | process.env['INSTANCE_ID'] = `${process.env.instanceId}`; 16 | process.env['REGION'] = `${process.env.region}`; 17 | process.env['AWS_REGION'] = `${process.env.region}`; 18 | process.env['ACCOUNT_NUMBER'] = `${process.env.accountNumber}`; 19 | process.env['CLOUDWATCH_NAMESPACE'] = `${process.env.service}`; 20 | 21 | // console.info(`Made environment: ` + JSON.stringify(process.env, null, 2)); 22 | } 23 | 24 | module.exports = { loadEnv } 25 | -------------------------------------------------------------------------------- /test/interactive/MessageTest.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | "use strict"; 5 | var rewire = require('rewire'); 6 | const expect = require('chai').expect; 7 | const interactiveConfig = require('./InteractiveConfig.js'); 8 | const messageInteractive = rewire('../../lambda/interactive/Message.js'); 9 | 10 | /** 11 | * Interactive tests for Message 12 | */ 13 | describe('MessageTests', function() 14 | { 15 | this.beforeAll(function () { 16 | interactiveConfig.loadEnv(); 17 | }); 18 | 19 | it('Message.execute() should succeed', async function() { 20 | 21 | var context = makeTestContext(); 22 | 23 | var response = await messageInteractive.execute(context); 24 | 25 | expect(response.message).to.equal('This is the biz, so cool.'); 26 | expect(response.inputRequired).to.equal(false); 27 | expect(response.contactId).to.equal('test'); 28 | expect(response.ruleSet).to.equal('My test rule set'); 29 | expect(response.rule).to.equal('My message rule'); 30 | expect(response.ruleType).to.equal('Message'); 31 | expect(response.audio).to.equal(undefined); 32 | expect(context.stateToSave.size).to.equal(0); 33 | }); 34 | 35 | it('Message.execute() should fail for invalid context', async function() { 36 | 37 | var context = { 38 | customerState: {} 39 | }; 40 | 41 | try 42 | { 43 | var response = await messageInteractive.execute(context); 44 | fail('Message should fail invalid context'); 45 | } 46 | catch (error) 47 | { 48 | expect(error.message).to.equal('Message.execute() missing required config'); 49 | } 50 | }); 51 | 52 | 53 | it('Message.input() should fail', async function() { 54 | 55 | var context = makeTestContext(); 56 | 57 | try 58 | { 59 | var response = await messageInteractive.input(context); 60 | fail('Message should not implement input()'); 61 | } 62 | catch (error) 63 | { 64 | expect(error.message).to.equal('Message.input() is not implemented'); 65 | } 66 | }); 67 | 68 | it('Message.confirm() should fail', async function() { 69 | 70 | var context = makeTestContext(); 71 | 72 | try 73 | { 74 | var response = await messageInteractive.confirm(context); 75 | fail('Message should not implement confirm()'); 76 | } 77 | catch (error) 78 | { 79 | expect(error.message).to.equal('Message.confirm() is not implemented'); 80 | } 81 | }); 82 | 83 | }); 84 | 85 | /** 86 | * Makes a test context 87 | */ 88 | function makeTestContext() 89 | { 90 | return { 91 | requestMessage: { 92 | contactId: 'test', 93 | generateVoice: false 94 | }, 95 | currentRuleSet: { 96 | name: 'My test rule set' 97 | }, 98 | currentRule: { 99 | name: 'My message rule', 100 | type: 'Message', 101 | params: { 102 | message: 'This is the biz, so cool. XXX' 103 | } 104 | }, 105 | customerState: { 106 | CurrentRule_message: 'This is the biz, so cool.' 107 | }, 108 | stateToSave: new Set() 109 | }; 110 | } 111 | -------------------------------------------------------------------------------- /test/interactive/TerminateTest.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | var rewire = require('rewire'); 5 | const expect = require('chai').expect; 6 | const interactiveConfig = require('./InteractiveConfig.js'); 7 | const terminateInteractive = rewire('../../lambda/interactive/Terminate.js'); 8 | 9 | /** 10 | * Interactive tests for Terminate 11 | */ 12 | describe('TerminateTests', function() 13 | { 14 | this.beforeAll(function () { 15 | interactiveConfig.loadEnv(); 16 | }); 17 | 18 | it('Terminate.execute() should succeed', async function() 19 | { 20 | var context = makeTestContext(); 21 | 22 | var response = await terminateInteractive.execute(context); 23 | 24 | expect(response.message).to.equal(undefined); 25 | expect(response.inputRequired).to.equal(false); 26 | expect(response.contactId).to.equal('test'); 27 | expect(response.ruleSet).to.equal('My test rule set'); 28 | expect(response.rule).to.equal('My terminate rule'); 29 | expect(response.ruleType).to.equal('Terminate'); 30 | expect(response.audio).to.equal(undefined); 31 | expect(response.terminate).to.equal(true); 32 | expect(context.stateToSave.size).to.equal(0); 33 | }); 34 | 35 | it('Terminate.execute() should fail for invalid context', async function() 36 | { 37 | var context = { 38 | }; 39 | 40 | try 41 | { 42 | var response = await terminateInteractive.execute(context); 43 | fail('UpdateState should fail invalid context'); 44 | } 45 | catch (error) 46 | { 47 | expect(error.message).to.equal('Terminate.execute() missing required config'); 48 | } 49 | }); 50 | 51 | it('Terminate.input() should fail', async function() 52 | { 53 | var context = makeTestContext(); 54 | 55 | try 56 | { 57 | var response = await terminateInteractive.input(context); 58 | fail('Terminate should not implement input()'); 59 | } 60 | catch (error) 61 | { 62 | expect(error.message).to.equal('Terminate.input() is not implemented'); 63 | } 64 | }); 65 | 66 | it('Terminate.confirm() should fail', async function() 67 | { 68 | var context = makeTestContext(); 69 | 70 | try 71 | { 72 | var response = await terminateInteractive.confirm(context); 73 | fail('Terminate should not implement confirm()'); 74 | } 75 | catch (error) 76 | { 77 | expect(error.message).to.equal('Terminate.confirm() is not implemented'); 78 | } 79 | }); 80 | 81 | }); 82 | 83 | /** 84 | * Makes a test context 85 | */ 86 | function makeTestContext() 87 | { 88 | return { 89 | requestMessage: { 90 | contactId: 'test', 91 | generateVoice: false 92 | }, 93 | currentRuleSet: { 94 | name: 'My test rule set' 95 | }, 96 | currentRule: { 97 | name: 'My terminate rule', 98 | type: 'Terminate', 99 | params: { 100 | } 101 | }, 102 | customerState: { 103 | }, 104 | stateToSave: new Set() 105 | }; 106 | } 107 | -------------------------------------------------------------------------------- /test/utils/LambdaMocker.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | const AWS = require('aws-sdk'); 5 | 6 | /** 7 | * This sets up a Lambda runtime mocked by aws-sdk-mock 8 | */ 9 | module.exports.setupMockLambda = function (AWSMock, lambdaUtils) 10 | { 11 | AWSMock.mock('Lambda', 'invoke', (function (params, callback) 12 | { 13 | console.info('Intercepting Lambda.invoke() request: ' + JSON.stringify(params, null, 2)); 14 | callback(null, makeInvokeResponse(params)); 15 | })); 16 | 17 | lambdaUtils.setLambda(new AWS.Lambda()); 18 | } 19 | 20 | function makeInvokeResponse(params) 21 | { 22 | var response = { 23 | StatusCode: 200, 24 | ExecutedVersion: '$LATEST', 25 | Payload: params.Payload 26 | }; 27 | 28 | console.info('makeInvokeResponse() Making response: ' + JSON.stringify(response, null, 2)); 29 | 30 | return response; 31 | } 32 | -------------------------------------------------------------------------------- /test/utils/config.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | "use strict"; 5 | 6 | const loadEnv = function (env) { 7 | process.env['VALID_ORIGINS'] = '["https://unit-testing-aws.cloudfront.net"]'; 8 | process.env['USERS_TABLE'] = "unittesting-rules-engine-users-ddb"; 9 | process.env['RULE_SETS_TABLE'] = "unittesting-rules-engine-rule-sets-ddb"; 10 | process.env['RULES_TABLE'] = "unittesting-rules-engine-rules-ddb"; 11 | process.env['CONFIG_TABLE'] = "unittesting-rules-engine-config-ddb"; 12 | process.env['STATE_TABLE'] = "unittesting-rules-engine-state-ddb"; 13 | process.env['TESTS_TABLE'] = "unittesting-rules-engine-tests-ddb"; 14 | process.env['CALLBACK_TABLE'] = "unittesting-rules-engine-callback-ddb"; 15 | process.env['SERVICE'] = "rules-engine"; 16 | process.env['STAGE'] = "unittesting"; 17 | } 18 | 19 | module.exports = { loadEnv } 20 | -------------------------------------------------------------------------------- /web/config/.gitignore: -------------------------------------------------------------------------------- 1 | site_config.json 2 | 3 | -------------------------------------------------------------------------------- /web/docs/INS207_Attribute_Routing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/docs/INS207_Attribute_Routing.pdf -------------------------------------------------------------------------------- /web/docs/INS207_Attribute_Routing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/docs/INS207_Attribute_Routing.png -------------------------------------------------------------------------------- /web/img/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/architecture.png -------------------------------------------------------------------------------- /web/img/aws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/aws.png -------------------------------------------------------------------------------- /web/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/favicon.png -------------------------------------------------------------------------------- /web/img/help/integration_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/help/integration_architecture.png -------------------------------------------------------------------------------- /web/img/help/rules_engine_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/help/rules_engine_architecture.png -------------------------------------------------------------------------------- /web/img/help/training_installation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/help/training_installation.png -------------------------------------------------------------------------------- /web/img/help/training_lex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/help/training_lex.png -------------------------------------------------------------------------------- /web/img/help/training_weights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/help/training_weights.png -------------------------------------------------------------------------------- /web/img/icons/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/admin.png -------------------------------------------------------------------------------- /web/img/icons/agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/agent.png -------------------------------------------------------------------------------- /web/img/icons/apigateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/apigateway.png -------------------------------------------------------------------------------- /web/img/icons/aws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/aws.png -------------------------------------------------------------------------------- /web/img/icons/cloudfront.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/cloudfront.png -------------------------------------------------------------------------------- /web/img/icons/cloudwatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/cloudwatch.png -------------------------------------------------------------------------------- /web/img/icons/computer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/computer.png -------------------------------------------------------------------------------- /web/img/icons/connect_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/connect_blue.png -------------------------------------------------------------------------------- /web/img/icons/connect_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/connect_red.png -------------------------------------------------------------------------------- /web/img/icons/customer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/customer.png -------------------------------------------------------------------------------- /web/img/icons/customers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/customers.png -------------------------------------------------------------------------------- /web/img/icons/dynamodb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/dynamodb.png -------------------------------------------------------------------------------- /web/img/icons/fire-users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/fire-users.png -------------------------------------------------------------------------------- /web/img/icons/lambda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/lambda.png -------------------------------------------------------------------------------- /web/img/icons/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/phone.png -------------------------------------------------------------------------------- /web/img/icons/queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/queue.png -------------------------------------------------------------------------------- /web/img/icons/ruleset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/ruleset.png -------------------------------------------------------------------------------- /web/img/icons/ruleset_unselected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/ruleset_unselected.png -------------------------------------------------------------------------------- /web/img/icons/s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/s3.png -------------------------------------------------------------------------------- /web/img/icons/terminate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/terminate.png -------------------------------------------------------------------------------- /web/img/icons/users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/users.png -------------------------------------------------------------------------------- /web/img/icons/users_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/icons/users_blue.png -------------------------------------------------------------------------------- /web/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/loading.gif -------------------------------------------------------------------------------- /web/img/loading_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/loading_small.gif -------------------------------------------------------------------------------- /web/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/logo.png -------------------------------------------------------------------------------- /web/img/progress_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/progress_blue.png -------------------------------------------------------------------------------- /web/img/progress_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/progress_green.png -------------------------------------------------------------------------------- /web/img/progress_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/progress_grey.png -------------------------------------------------------------------------------- /web/img/progress_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/progress_red.png -------------------------------------------------------------------------------- /web/img/progress_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-rules-engine/592b65ade1e645a246be6a1d5cb7284fd40ba6ed/web/img/progress_yellow.png -------------------------------------------------------------------------------- /web/templates/login.hbs: -------------------------------------------------------------------------------- 1 | 28 | 29 | 54 | -------------------------------------------------------------------------------- /web/templates/logout.hbs: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /web/templates/navigation.hbs: -------------------------------------------------------------------------------- 1 | 36 | 40 | {{else}} 41 | 44 | 47 | 48 | 51 | {{/if}} 52 | --------------------------------------------------------------------------------