├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.jp.md ├── README.md ├── architecture.drawio ├── deployment ├── contact-surveys-amazon-connect.yaml ├── frontend │ ├── asset-manifest.json │ ├── config.js │ ├── favicon.ico │ ├── favicon.png │ ├── index.html │ ├── robots.txt │ └── static │ │ ├── css │ │ ├── main.09784eec.css │ │ └── main.09784eec.css.map │ │ ├── js │ │ ├── 787.91799eaa.chunk.js │ │ ├── 787.91799eaa.chunk.js.map │ │ ├── main.3336631e.js │ │ ├── main.3336631e.js.LICENSE.txt │ │ └── main.3336631e.js.map │ │ └── media │ │ └── ico_connect.c4b4b06e46441b63ec178326f8a9e34e.svg └── uuid-layer.zip ├── docs ├── README.md ├── api │ ├── README.md │ └── openapi.yaml ├── architecture │ ├── README.md │ ├── component-diagram.md │ └── data-flow.md ├── backend │ ├── README.md │ └── lambda-functions.md ├── deployment │ ├── README.md │ └── cloudformation-parameters.md ├── frontend │ ├── README.md │ └── components.md ├── infra.dot ├── infra.svg └── user-guides │ ├── README.md │ ├── admin-guide.md │ └── contact-center-manager-guide.md ├── examples ├── 1-Survey Example Disconnect └── 2-Simple Survey Example ├── generate-react-cli.json ├── img ├── Architecture-ExperienceBuilder.png ├── Architecture.png ├── application-deploy-success.png ├── cf-stack-params.png ├── cloudformation-launch-stack.png ├── simple-survey-example-definition.png ├── simple-survey-example-disconnect-flow.png ├── simple-survey-example-flag.png ├── simple-survey-example-id.png ├── simple-survey-example-inbound-flow-1.png ├── simple-survey-example-inbound-flow-2.png ├── simple-survey-example-questions.png ├── simple-survey-example-results.png └── simple-survey-example-task-description.png ├── lambdas ├── api │ └── index.js ├── cognitoValidateUser │ └── index.js ├── getSurveyConfig │ └── index.js ├── processReviewFlags │ └── index.js ├── surveyUtils │ └── index.js └── writeSurveyResults │ └── index.js ├── package-lock.json ├── package.json ├── public ├── config.js ├── favicon.ico ├── favicon.png ├── index.html └── robots.txt ├── src ├── .env.production ├── App.css ├── App.test.tsx ├── App.tsx ├── components │ ├── Authentication │ │ ├── Authentication.css │ │ └── Authentication.tsx │ ├── ChangePassword │ │ ├── ChangePassword.css │ │ └── ChangePassword.tsx │ ├── ForgotPassword │ │ └── ForgotPassword.tsx │ ├── Home │ │ └── Home.tsx │ ├── Logout │ │ ├── Logout.css │ │ └── Logout.tsx │ ├── Survey │ │ └── Survey.tsx │ └── SurveysList │ │ └── SurveysList.tsx ├── ico_connect.svg ├── index.css ├── index.tsx ├── models │ └── SurveyModel.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts └── setupTests.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /lambdas/*/*.zip 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.development 18 | .env.test.local 19 | .env.production.local 20 | cfn_nag_output.txt 21 | npm_audit_results.txt 22 | .vscode 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.1.0] - Pre-release 8 | 9 | Intial commit prior to public release. 10 | 11 | ## [1.0.0] - Initial release 12 | 13 | ## [1.0.1] - API Gateway logging fix 14 | Fix for issue with deployment when users try to deploy in an AWS account that has not been configured for API Gateway logging. 15 | 16 | ## [1.0.2] - Results filtering 17 | Fix for results not being filtered properly when choosing to filter by date. 18 | 19 | ## [1.1] - Added support for CHAT, auto-verify admin user, and update to Node 16 20 | Added support for surveys to accomodate the CHAT channel. In addition, the "admin" user created when the solution is deployed will now have their email address automatically verified allowing for a more robust password management workflow. 21 | All runtimes for Lambda functions have also been updated to Node 16, with plans to update to Node 20 soon. 22 | 23 | ## [1.2] - nodejs22.x upgrade 24 | All Lambdas now run with nodejs22.x runtime. 25 | Architecture diagram updated to reflect chat support. 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.jp.md: -------------------------------------------------------------------------------- 1 | # Amazon Connect での顧客アンケート収集 2 | 3 | 顧客へのアンケートは、コンタクトセンターが提供する顧客体験やサービスを微調整していくための診断ツールとして重要なものです。これは問い合わせ体験への評価だけでなく、体験後の顧客の購買意欲や動機や意向を組織が理解するのにも役立ちます。 4 | 5 | このプロジェクトは、ユーザーがアンケートを作成・管理し、Amazon Connect でそのアンケートを使用し、結果を可視化する、一貫したソリューションを提供することを目的としています。 6 | ソリューションのデプロイ後、ユーザーは安全なウェブアプリケーションにアクセスしてアンケートを定義したり、既存のアンケートを参照/編集したり、アンケートごとの集計結果を視覚化したりすることができます。 7 | 8 | 9 | ソリューションの構成: 10 | - 管理用ウェブアプリケーション 11 | - アンケートの設定を保存するデータストア 12 | - アンケートの結果を保存するデータストア 13 | - 動的に、最適なアンケートを再生するコンタクトフローモジュール 14 | 15 | このソリューションを活用すると、以下のようなシナリオでアンケートを実施することができます: 16 | - 顧客との会話の後のアンケート 17 | - アウトバウンド型のアンケート (startOutboundContactAPIと組み合わせる) 18 | 19 | これらのシナリオの実装方法の詳細については [使用例](#使用例) を確認してください。 20 | 21 | ## アーキテクチャ 22 | 23 | ![Architecture](/img/Architecture.png) 24 | 25 | このソリューションは、必要なリソースをデプロイし、以下のように動作します: 26 | 27 | 1. Amazon S3 に保存されたフロントエンドの静的コンテンツを Amazon CloudFront を通じて公開し、Amazon Cognito によってユーザー管理を行います。 28 | 2. 必要なフローモジュールが Amazon Connect インスタンスにデプロイされます。 29 | 3. 管理者はウェブアプリケーションを使用して、必要に応じてアンケートを定義します。 30 | 4. アンケートの設定は Amazon DynamoDB に保存されます。 31 | 5. 問い合わせに対してフローモジュールが実行され、提供するアンケートを識別するためのコンタクト属性が設定されます。 32 | 6. コンタクトフローモジュールは、その問い合わせ用のアンケートの設定を取得します。 33 | 7. その問い合わせでアンケートが実施され、顧客はアンケートに回答します。 34 | 8. 個々の問い合わせのアンケート結果は Amazon DynamoDB テーブルに保存され、必要に応じて Amazon Connect Task が生成されます。 35 | 36 | ## デプロイ 37 | 38 | このソリューションをデプロイするには、以下の権限が必要です: 39 | - S3 バケットの作成と管理 40 | - Amazon DynamoDB リソースの作成と管理 41 | - Amazon API Gateway リソースの作成と管理 42 | - Amazon CloudFront リソースの作成と管理 43 | - Amazon Cognito リソースの作成と管理 44 | - AWS Lambda リソースの作成と管理 45 | - Amazon Connect リソースの作成と管理 46 | 47 | 通常、このソリューションのデプロイは、AWS 環境にフルアクセスできるユーザーで行います。 48 | 49 | 1. 「Launch Stack」 ボタンをクリックして、ご希望のリージョンにソリューションをデプロイします。これは Amazon Connect インスタンスがデプロイされているリージョンと同一である必要があります。 50 | 51 | [![cloudformation-launch-button](img/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?#/stacks/new?stackName=Production&templateURL=https://aws-contact-center-blog.s3.us-west-2.amazonaws.com/amazon-connect-post-call-surveys/contact-surveys-amazon-connect.yaml) 52 | 53 | 2. 必要なパラメータ: 54 | - このソリューションの初期ユーザーの メールアドレス 55 | - このソリューションで使用する Amazon Connect インスタンスの ARN 56 | - このソリューションで使用する Amazon Connect インスタンスのエイリアス 57 | - このソリューションが生成するタスクの送信先になるフローの ID 58 | 59 | **補足:** もし、このソリューションが生成するタスクを処理するためのフローが決まっていない場合は、Amazon Connect インスタンスでデフォルトで使用できる *Sample inbound flow (first contact experience)* の IDを指定してください。 60 | 61 | ![Cloudformation Stack parameters](/img/cf-stack-params.png) 62 | 63 | 3. スタックの作成 ボタンをクリックして処理を進めます。 64 | 65 | **補足:** スタックのデプロイが完了するまで約 5 分かかります。また、ユーザー名と一時パスワードが記載されたメールが届きます。 66 | 67 | 4. スタックがデプロイされたら、 **出力** タブを開いて **AdminUser** の値とこのアプリケーションの URL をメモします。 68 | 69 | 5. メモした URL に移動して、上で確認した *Username* でログインします。 *password* はメールで受け取った仮パスワードを入力します。 70 | 71 | **補足:** 初回ログイン時にパスワードの変更を求められます。 72 | 73 | 6. 以下の画面が表示されたら、ソリューションは正常にデプロイされています。 74 | 75 | ![Application Deployed Successfully](/img/application-deploy-success.png) 76 | 77 | ## 使用例 78 | 79 | 以下の例は、このソリューションをどのように使用できるかを理解するのに役立ちます。例を見ていく前に、ソリューションのデプロイが完了していることを確認してください。 80 | 81 | ### シンプルな応対後のアンケート(post-contact survey)を行い、低評価の結果にはレビューのためのフラグを立てる 82 | 83 | この例では、顧客の回答が低い場合に Amazon Connect Task を通じてスーバーバイザーにアラートを送信する、簡単な post-contact survey の実装方法を学びます。 84 | 85 | 1. このソリューションによってデプロイされた Contact Surveys for Amazon Connect アプリケーションを使ってアンケートを作成します。 86 | 87 | ![Simple survey example definition](/img/simple-survey-example-definition.png) 88 | 89 | 1. アンケートに質問をいくつか追加します。 90 | 91 | ![Simple survey example questions](/img/simple-survey-example-questions.png) 92 | 93 | 3. どれか一つの質問で, **Additional settings** をクリックし、 **Flag for review** にチェックを入れて閾値を設定します。 94 | 95 | ![Simple survey example flag](/img/simple-survey-example-flag.png) 96 | 97 | この閾値を下回るスコアが入力されると、Amazon Connect のタスクが開始されます。このタスクは、ソリューションをデプロイしたときに指定したフローにルーティングされます。 98 | 99 | 4. Save をクリックします。一覧をリフレッシュし、新しく作成したアンケートの **Id** をメモします。 100 | 101 | ![Simple survey example id](/img/simple-survey-example-id.png) 102 | 103 | **補足:** 皆さんの環境で作成したアンケートの Id は、このスクリーンショットとは違うものになります。 104 | 105 | 5. 皆さんの Amazon Connect で新しいフローを作成し、[*Survey Example Disconnect* フロー](/examples/1-Survey%20Example%20Disconnect) をインポートします。フローを公開する前に、「**呼び出しモジュール**」ブロックで、皆さんのインスタンスに作成されている *Contact Survey* が指定されていることを確認します。 106 | 107 | **補足:** サンプルフローは "examples" フォルダー内にあります。 108 | 109 | ![Simple survey example disconnect flow](/img/simple-survey-example-disconnect-flow.png) 110 | 111 | 6. ステップ 5 と同様に、[*Simple Survey Example* フロー](/examples/2-Simple%20Survey%20Example) をインポートします。いくつか設定変更する必要があるので、ここではまだ公開はしません。 112 | 113 | **補足:** サンプルフローは "examples" フォルダー内にあります。 114 | 115 | 7. 「*切断フローを設定する*」ブロックをクリックします。 このブロック内で、切断フローとしてステップ 5 でインポートした *Survey Example Disconnect* を設定します。 116 | 117 | ![Simple survey example set disconnect flow](/img/simple-survey-example-inbound-flow-1.png) 118 | 119 | 8. 「**コンタクト属性の設定**」ブロックをクリックします。このブロックでは *surveyId* というコンタクト属性を設定します。*surveyId* の*値*欄に、ステップ 4 でメモした *Id* をペーストします。 120 | 121 | ![Simple survey example set contact attribute](/img/simple-survey-example-inbound-flow-2.png) 122 | 123 | **補足:** ある問い合わせにおいて再生されるアンケートは、*surveyId* コンタクト属性に基づいて決定されると言うことを理解することが重要です。ここでは、コンタクトフローでその値を明示的に設定することで、再生するアンケートを静的に定義しています。もちろん、IVR での選択、連絡先の転送先のキュー、その連絡先に一致するコンタクトレンズのカテゴリー、またはその他のコンタクト属性などに基づいて、この属性を動的に設定することもできます。 124 | 125 | 9. フローを保存して、公開します。 126 | 127 | **補足:** このフローで処理されたコンタクトは、*BasicQueue* にキューイングされます。別のキューでテストを実行したい場合は、**作業キューの設定** ブロックを適宜調整してください。 128 | 129 | 10. *Simple Survey Example* を、テストに使用する任意の DID 番号に紐づけます。 130 | 131 | 11. テスト用に選択した番号に電話をかけます。このアンケートは、顧客との対話の最後に実行するので、コールが発信されるキューに受付可のエージェントがいることを確認してください。 132 | 133 | 12. 顧客側が電話につながっている間に、エージェント側の通話を終了します。顧客はアンケートに誘導されます。 134 | 135 | 13. 顧客側は最初の質問を聞いたら、*2*(すなわち、ステップ3で定義された閾値より低いスコア)と答えます。2番目の質問に対しては、0から5の間の任意の数字を入力します。 136 | 137 | 14. 顧客が最初の質問に対して低いスコアをつけたので、スーパーバイザーがレビューするためのタスクが作成されます。このタスクは、ソリューションのデプロイ時に定義されたコンタクトフローによって処理されます。 138 | 139 | **補足:** ここでの例では、すべての Amazon Connect インスタンスで利用可能な *Sample inbound flow (first contact experience)* を使用することにしました。このフローは、タスクを *BasicQueue* に誘導します。実際のシナリオでは、ニーズに応じてこれらのタスクの配信ロジックを処理する特定のコンタクトフローを作成してください。 140 | 141 | 15. このタスクはスーパーバイザーによって受信され、そこには低評価の会話の詳細が含まれています。 142 | 143 | ![Simple survey example task](/img/simple-survey-example-task-description.png) 144 | 145 | 16. Contact Surveys for Amazon Connect アプリケーションを使用して、アンケートの集計結果を可視化することができます。アンケートを選択し、**Results**タブに移動するだけです。また、**Export** ボタンをクリックして、個々の結果をエクスポートすることもできます。 146 | 147 | ![Simple survey example results](/img/simple-survey-example-results.png) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Contact Surveys for Amazon Connect 2 | 3 | Surveys are important as a diagnostic tool to fine-tune the experience and service delivered. They not only assess perceptions of experiences, but also help an organization understand customer motivations and intentions following the experience. 4 | 5 | This project is aimed at delivering an end-to-end solution that will enable users to create and manage surveys, use these surveys with their Amazon Connect contact centre, and visualise their results. 6 | Once deployed, users can access a secure web application to define surveys, consult / edit existing surveys, and visualise aggregated results per survey. 7 | 8 | The solution is composed of: 9 | - a web application for management 10 | - a data-store to hold survey configuration 11 | - a data-store to hold results 12 | - a Contact Flow Module that will dynamically play the right survey for to a contact 13 | 14 | Thanks to the flexible nature of this solution, surveys can be played in these scenario: 15 | - reactively, after a contact (inbound voice, outbound voice, chat) with a customer 16 | - proactively, in combination with the startOutboundContact API 17 | 18 | To find more information about how to implement these scenario, see [Example usage](#example-usage) 19 | 20 | ## Architecture 21 | 22 | ![Architecture](/img/Architecture.png) 23 | 24 | The solution deploys the required resources and follow the pattern: 25 | 26 | 1. Amazon S3 stores and serves the frontend static content through Amazon Cloudfront and restricted by Amazon Cognito for user management 27 | 2. The required Contact Flow Module is deployed in the Amazon Connect instance 28 | 3. Administrators use the web application to define contact surveys according to their needs 29 | 4. The configuration of the surveys is stored in Amazon DynamoDB 30 | 5. When required, the Contact Flow Module is invoked for a contact, with a contact attribute set to identify the survey to be offered 31 | 6. The Contact Flow Module retrieves the configuration of the survey for the contact 32 | 7. The contact is offered the survey, and the survey is answered 33 | 8. Results are stored in an Amazon DynamoDB table for the individual contact and if required, an Amazon Connect Task is created 34 | 35 | ## Deployment 36 | 37 | To deploy this solution, you will need to have the following permissions in your AWS account: 38 | - Create and manage S3 buckets 39 | - Create and manage Amazon DynamoDB resources 40 | - Create and manage AWS API Gateway resources 41 | - Create and manage Amazon Cloudfront resources 42 | - Create and manage Amazon Cognito resources 43 | - Create and manage AWS Lambda resources 44 | - Create and manage Amazon Connect resources 45 | - Create and manage Lex bots 46 | 47 | Typically, this solution should be deployed by a user with full access to your AWS environment. 48 | 49 | 1. Click on the "Launch Stack" button to deploy the solution in your preferred region. This will be he same region that was used to deploy your Amazon Connect instance. 50 | 51 | [![cloudformation-launch-button](img/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?#/stacks/new?stackName=Production&templateURL=https://aws-contact-center-blog.s3.us-west-2.amazonaws.com/amazon-connect-post-call-surveys/contact-surveys-amazon-connect.yaml) 52 | 53 | 2. Provide the required parameters: 54 | - an email address for the initial user of the solution 55 | - the ARN of the Amazon Connect instance you want to use with this solution 56 | - the alias of the Amazon Connect instance you want to use with this solution 57 | - the ID of the Contact Flow to which task created by this solution will be sent to 58 | 59 | **Note:** If you are unsure about which Contact Flow to choose to process the tasks generated by the solution, use the ID of the *Sample inbound flow (first contact experience)* available by default in your Amazon Connect instance. 60 | 61 | ![CloudFormation Stack parameters](/img/cf-stack-params.png) 62 | 63 | 3. Proceed with the stack creation steps. 64 | 65 | **Note:** It will take approximately 5 minutes for the stack to complete the deployment. You will receive an email containing a temporary password. 66 | 67 | 4. Once the stack is deployed, in the **Output** tab, note the value of the **AdminUser** and the URL of the application. 68 | 69 | 5. Navigate to the URL noted just above. Log in to the application with the *Username* collected in the output of the Cloudformation stack, and the *password* received during the deployment. 70 | 71 | **Note:** You will be required to change your password at first login. 72 | 73 | 6. If you see the following screen, the solution has been successfully deployed, and you are good to go. 74 | 75 | ![Application Deployed Successfully](/img/application-deploy-success.png) 76 | 77 | ## Example usage 78 | 79 | The following examples will help you understand how you can use this solution to cater for typical use cases. Make sure you have deployed the solution before going through each of the examples. 80 | 81 | ### Simple post-contact survey, with low-score flagged for review 82 | 83 | In this example, you will learn to implement a simple post-contact survey that will alert a supervisor through an Amazon Connect Task every time a customer replies to a given question with a low score. 84 | 85 | 1. Create a new survey using the Contact Surveys for Amazon Connect application deployed with the solution: 86 | 87 | ![Simple survey example definition](/img/simple-survey-example-definition.png) 88 | 89 | 2. Add a couple of questions to your survey: 90 | 91 | ![Simple survey example questions](/img/simple-survey-example-questions.png) 92 | 93 | 3. For one of these questions, select **Additional settings**, tick the **Flag for review**, and define the threshold: 94 | 95 | ![Simple survey example flag](/img/simple-survey-example-flag.png) 96 | 97 | Any contact that inputs a score lower than the defined threshold to that question will trigger a task in Amazon Connect. This task will be routed through the Contact Flow defined when the solution was deployed. 98 | 99 | 4. Save, refresh the list, and note the **Id** of your new survey. 100 | 101 | ![Simple survey example ID](/img/simple-survey-example-id.png) 102 | 103 | **Note:** the ID of your survey will be different from the one on the screenshot. 104 | 105 | 5. In you Amazon Connect instance, create a new Contact Flow and import the [*Survey Example Disconnect* flow](/examples/1-Survey%20Example%20Disconnect). Before publishing, make sure that the **Invoke module** block is pointing to the *Contact Survey* module available in your Amazon Connect instance. 106 | 107 | **Note:** the contact flow can be found in the "examples" folder 108 | 109 | ![Simple survey example disconnect flow](/img/simple-survey-example-disconnect-flow.png) 110 | 111 | 6. Repeat step 5 to import the [*Simple Survey Example* flow](/examples/2-Simple%20Survey%20Example). Don't publish it just yet, you will need to make some configuration adjustments. 112 | 113 | **Note:** the contact flow can be found in the "examples" folder 114 | 115 | 7. Locate the *Set Disconnect Flow* block. Configure this block to set the disconnect flow to the *Survey Example Disconnect* flow imported at step 5. 116 | 117 | ![Simple survey example set disconnect flow](/img/simple-survey-example-inbound-flow-1.png) 118 | 119 | 8. Locate the **Set Contact Attributes** block. This block sets a single *surveyId* contact attribute. Paste the *id* of your survey (noted at step 4) in the *Value* field of the *surveyId* contact attribute. 120 | 121 | ![Simple survey example set contact attribute](/img/simple-survey-example-inbound-flow-2.png) 122 | 123 | **Note:** It is important to understand that the survey that will be played to a contact is based on the value of the *surveyId* contact attribute. Here, we are defining the survey to play for a contact in a static fashion by explicitly setting its value in the contact flow. You could of course set this attribute dynamically based on, for example, choices made in an IVR, the queue the contact was transferred to, Contact Lens categories matched for that contact, or any other contact attributes. 124 | 125 | 9. Save and publish the flow. 126 | 127 | **Note:** contacts processed by this flow will be queued on the *BasicQueue* which is available in your Amazon Connect instance. If you want to execute the test with a different queue, adjust the **Set working queue** block accordingly. 128 | 129 | 10. Associate the *Simple Survey Example* contact flow to any DID available to you for testing. 130 | 131 | 11. Place a call to the number you have selected for testing. Since we are intending to run this survey at the end of the customer interaction, make sure you have an agent available on the queue where the call is going to be directed. 132 | 133 | 12. While the customer stays on the line, end the call for the agent's end. The customer will be directed to the survey. 134 | 135 | 13. The customer hears the first question, and answer *2* (i.e. a score lower than the threshold defined at step 3). For the second question, the customer enters any digit between 0 and 5. 136 | 137 | 14. Since the customer has given a low score to our first question, a task is created for a supervisor to review. This task is processed by the Contact Flow defined during the deployment of the solution. 138 | 139 | **Note:** for the purpose of this example, we have decided to use the *Sample inbound flow (first contact experience)* that is available to all Amazon Connect instances. This flow will direct the task to the *BasicQueue*. In a real world scenario, create a specific contact flow to handle the distribution logic of these tasks according to your needs. 140 | 141 | 15. The task is received by a supervisor and contains the details of the interaction that was poorly rated. 142 | 143 | ![Simple survey example task](/img/simple-survey-example-task-description.png) 144 | 145 | 16. You can visualise the aggregated results for your survey using the Contact Surveys for Amazon Connect application. Simply select the survey, and navigate to the **Results** tab. You can also export the individual results by clicking on the **Export** button. 146 | 147 | ![Simple survey example results](/img/simple-survey-example-results.png) 148 | -------------------------------------------------------------------------------- /deployment/frontend/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.09784eec.css", 4 | "main.js": "/static/js/main.3336631e.js", 5 | "static/js/787.91799eaa.chunk.js": "/static/js/787.91799eaa.chunk.js", 6 | "static/media/ico_connect.svg": "/static/media/ico_connect.c4b4b06e46441b63ec178326f8a9e34e.svg", 7 | "index.html": "/index.html", 8 | "main.09784eec.css.map": "/static/css/main.09784eec.css.map", 9 | "main.3336631e.js.map": "/static/js/main.3336631e.js.map", 10 | "787.91799eaa.chunk.js.map": "/static/js/787.91799eaa.chunk.js.map" 11 | }, 12 | "entrypoints": [ 13 | "static/css/main.09784eec.css", 14 | "static/js/main.3336631e.js" 15 | ] 16 | } -------------------------------------------------------------------------------- /deployment/frontend/config.js: -------------------------------------------------------------------------------- 1 | // this file will be overwritten when the CFT stack is deployed 2 | 3 | window.app_configuration = { 4 | cognito_pool_id: "", 5 | cognito_client_id: "", 6 | api_endpoint: "", 7 | }; 8 | 9 | -------------------------------------------------------------------------------- /deployment/frontend/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/deployment/frontend/favicon.ico -------------------------------------------------------------------------------- /deployment/frontend/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/deployment/frontend/favicon.png -------------------------------------------------------------------------------- /deployment/frontend/index.html: -------------------------------------------------------------------------------- 1 | Contact Surveys for Amazon Connect
-------------------------------------------------------------------------------- /deployment/frontend/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /deployment/frontend/static/js/787.91799eaa.chunk.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkamazon_connect_surveys_react=self.webpackChunkamazon_connect_surveys_react||[]).push([[787],{787:function(e,n,t){t.r(n),t.d(n,{getCLS:function(){return y},getFCP:function(){return g},getFID:function(){return C},getLCP:function(){return P},getTTFB:function(){return D}});var i,r,a,o,u=function(e,n){return{name:e,value:void 0===n?-1:n,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(e,n){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var t=new PerformanceObserver((function(e){return e.getEntries().map(n)}));return t.observe({type:e,buffered:!0}),t}}catch(e){}},f=function(e,n){var t=function t(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),n&&(removeEventListener("visibilitychange",t,!0),removeEventListener("pagehide",t,!0)))};addEventListener("visibilitychange",t,!0),addEventListener("pagehide",t,!0)},s=function(e){addEventListener("pageshow",(function(n){n.persisted&&e(n)}),!0)},m=function(e,n,t){var i;return function(r){n.value>=0&&(r||t)&&(n.delta=n.value-(i||0),(n.delta||void 0===i)&&(i=n.value,e(n)))}},v=-1,p=function(){return"hidden"===document.visibilityState?0:1/0},d=function(){f((function(e){var n=e.timeStamp;v=n}),!0)},l=function(){return v<0&&(v=p(),d(),s((function(){setTimeout((function(){v=p(),d()}),0)}))),{get firstHiddenTime(){return v}}},g=function(e,n){var t,i=l(),r=u("FCP"),a=function(e){"first-contentful-paint"===e.name&&(f&&f.disconnect(),e.startTime-1&&e(n)},r=u("CLS",0),a=0,o=[],v=function(e){if(!e.hadRecentInput){var n=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-n.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,t())}},p=c("layout-shift",v);p&&(t=m(i,r,n),f((function(){p.takeRecords().map(v),t(!0)})),s((function(){a=0,T=-1,r=u("CLS",0),t=m(i,r,n)})))},E={passive:!0,capture:!0},w=new Date,L=function(e,n){i||(i=n,r=e,a=new Date,F(removeEventListener),S())},S=function(){if(r>=0&&r1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,n){var t=function(){L(e,n),r()},i=function(){r()},r=function(){removeEventListener("pointerup",t,E),removeEventListener("pointercancel",i,E)};addEventListener("pointerup",t,E),addEventListener("pointercancel",i,E)}(n,e):L(n,e)}},F=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(n){return e(n,b,E)}))},C=function(e,n){var t,a=l(),v=u("FID"),p=function(e){e.startTimeperformance.now())return;t.entries=[n],e(t)}catch(e){}},"complete"===document.readyState?setTimeout(n,0):addEventListener("load",(function(){return setTimeout(n,0)}))}}}]); 2 | //# sourceMappingURL=787.91799eaa.chunk.js.map -------------------------------------------------------------------------------- /deployment/frontend/static/js/787.91799eaa.chunk.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"static/js/787.91799eaa.chunk.js","mappings":"6SAAA,IAAIA,EAAEC,EAAEC,EAAEC,EAAEC,EAAE,SAASJ,EAAEC,GAAG,MAAM,CAACI,KAAKL,EAAEM,WAAM,IAASL,GAAG,EAAEA,EAAEM,MAAM,EAAEC,QAAQ,GAAGC,GAAG,MAAMC,OAAOC,KAAKC,MAAM,KAAKF,OAAOG,KAAKC,MAAM,cAAcD,KAAKE,UAAU,MAAO,EAACC,EAAE,SAAShB,EAAEC,GAAG,IAAI,GAAGgB,oBAAoBC,oBAAoBC,SAASnB,GAAG,CAAC,GAAG,gBAAgBA,KAAK,2BAA2BoB,MAAM,OAAO,IAAIlB,EAAE,IAAIe,qBAAqB,SAASjB,GAAG,OAAOA,EAAEqB,aAAaC,IAAIrB,EAAG,IAAG,OAAOC,EAAEqB,QAAQ,CAACC,KAAKxB,EAAEyB,UAAS,IAAKvB,CAAE,CAAW,CAAV,MAAMF,GAAI,CAAC,EAAC0B,EAAE,SAAS1B,EAAEC,GAAG,IAAIC,EAAE,SAASA,EAAEC,GAAG,aAAaA,EAAEqB,MAAM,WAAWG,SAASC,kBAAkB5B,EAAEG,GAAGF,IAAI4B,oBAAoB,mBAAmB3B,GAAE,GAAI2B,oBAAoB,WAAW3B,GAAE,IAAM,EAAC4B,iBAAiB,mBAAmB5B,GAAE,GAAI4B,iBAAiB,WAAW5B,GAAE,EAAI,EAAC6B,EAAE,SAAS/B,GAAG8B,iBAAiB,YAAY,SAAS7B,GAAGA,EAAE+B,WAAWhC,EAAEC,EAAG,IAAE,EAAI,EAACgC,EAAE,SAASjC,EAAEC,EAAEC,GAAG,IAAIC,EAAE,OAAO,SAASC,GAAGH,EAAEK,OAAO,IAAIF,GAAGF,KAAKD,EAAEM,MAAMN,EAAEK,OAAOH,GAAG,IAAIF,EAAEM,YAAO,IAASJ,KAAKA,EAAEF,EAAEK,MAAMN,EAAEC,IAAK,CAAC,EAACiC,GAAG,EAAEC,EAAE,WAAW,MAAM,WAAWR,SAASC,gBAAgB,EAAE,GAAI,EAACQ,EAAE,WAAWV,GAAG,SAAS1B,GAAG,IAAIC,EAAED,EAAEqC,UAAUH,EAAEjC,CAAE,IAAE,EAAI,EAACqC,EAAE,WAAW,OAAOJ,EAAE,IAAIA,EAAEC,IAAIC,IAAIL,GAAG,WAAWQ,YAAY,WAAWL,EAAEC,IAAIC,GAAI,GAAE,EAAG,KAAI,CAAKI,sBAAkB,OAAON,CAAE,EAAE,EAACO,EAAE,SAASzC,EAAEC,GAAG,IAAIC,EAAEC,EAAEmC,IAAIZ,EAAEtB,EAAE,OAAO8B,EAAE,SAASlC,GAAG,2BAA2BA,EAAEK,OAAO+B,GAAGA,EAAEM,aAAa1C,EAAE2C,UAAUxC,EAAEqC,kBAAkBd,EAAEpB,MAAMN,EAAE2C,UAAUjB,EAAElB,QAAQoC,KAAK5C,GAAGE,GAAE,IAAM,EAACiC,EAAEU,OAAOC,aAAaA,YAAYC,kBAAkBD,YAAYC,iBAAiB,0BAA0B,GAAGX,EAAED,EAAE,KAAKnB,EAAE,QAAQkB,IAAIC,GAAGC,KAAKlC,EAAE+B,EAAEjC,EAAE0B,EAAEzB,GAAGkC,GAAGD,EAAEC,GAAGJ,GAAG,SAAS5B,GAAGuB,EAAEtB,EAAE,OAAOF,EAAE+B,EAAEjC,EAAE0B,EAAEzB,GAAG+C,uBAAuB,WAAWA,uBAAuB,WAAWtB,EAAEpB,MAAMwC,YAAYlC,MAAMT,EAAEkC,UAAUnC,GAAE,EAAI,GAAG,GAAG,IAAI,EAAC+C,GAAE,EAAGC,GAAG,EAAEC,EAAE,SAASnD,EAAEC,GAAGgD,IAAIR,GAAG,SAASzC,GAAGkD,EAAElD,EAAEM,KAAM,IAAG2C,GAAE,GAAI,IAAI/C,EAAEC,EAAE,SAASF,GAAGiD,GAAG,GAAGlD,EAAEC,EAAG,EAACiC,EAAE9B,EAAE,MAAM,GAAG+B,EAAE,EAAEC,EAAE,GAAGE,EAAE,SAAStC,GAAG,IAAIA,EAAEoD,eAAe,CAAC,IAAInD,EAAEmC,EAAE,GAAGjC,EAAEiC,EAAEA,EAAEiB,OAAO,GAAGlB,GAAGnC,EAAE2C,UAAUxC,EAAEwC,UAAU,KAAK3C,EAAE2C,UAAU1C,EAAE0C,UAAU,KAAKR,GAAGnC,EAAEM,MAAM8B,EAAEQ,KAAK5C,KAAKmC,EAAEnC,EAAEM,MAAM8B,EAAE,CAACpC,IAAImC,EAAED,EAAE5B,QAAQ4B,EAAE5B,MAAM6B,EAAED,EAAE1B,QAAQ4B,EAAElC,IAAK,CAAC,EAACiD,EAAEnC,EAAE,eAAesB,GAAGa,IAAIjD,EAAE+B,EAAE9B,EAAE+B,EAAEjC,GAAGyB,GAAG,WAAWyB,EAAEG,cAAchC,IAAIgB,GAAGpC,GAAE,EAAI,IAAG6B,GAAG,WAAWI,EAAE,EAAEe,GAAG,EAAEhB,EAAE9B,EAAE,MAAM,GAAGF,EAAE+B,EAAE9B,EAAE+B,EAAEjC,EAAG,IAAI,EAACsD,EAAE,CAACC,SAAQ,EAAGC,SAAQ,GAAIC,EAAE,IAAI/C,KAAKgD,EAAE,SAASxD,EAAEC,GAAGJ,IAAIA,EAAEI,EAAEH,EAAEE,EAAED,EAAE,IAAIS,KAAKiD,EAAE/B,qBAAqBgC,IAAK,EAACA,EAAE,WAAW,GAAG5D,GAAG,GAAGA,EAAEC,EAAEwD,EAAE,CAAC,IAAItD,EAAE,CAAC0D,UAAU,cAAczD,KAAKL,EAAEwB,KAAKuC,OAAO/D,EAAE+D,OAAOC,WAAWhE,EAAEgE,WAAWrB,UAAU3C,EAAEqC,UAAU4B,gBAAgBjE,EAAEqC,UAAUpC,GAAGE,EAAE+D,SAAS,SAASlE,GAAGA,EAAEI,EAAG,IAAGD,EAAE,EAAG,CAAC,EAACgE,EAAE,SAASnE,GAAG,GAAGA,EAAEgE,WAAW,CAAC,IAAI/D,GAAGD,EAAEqC,UAAU,KAAK,IAAI1B,KAAKmC,YAAYlC,OAAOZ,EAAEqC,UAAU,eAAerC,EAAEwB,KAAK,SAASxB,EAAEC,GAAG,IAAIC,EAAE,WAAWyD,EAAE3D,EAAEC,GAAGG,GAAI,EAACD,EAAE,WAAWC,GAAI,EAACA,EAAE,WAAWyB,oBAAoB,YAAY3B,EAAEqD,GAAG1B,oBAAoB,gBAAgB1B,EAAEoD,EAAG,EAACzB,iBAAiB,YAAY5B,EAAEqD,GAAGzB,iBAAiB,gBAAgB3B,EAAEoD,EAAG,CAAjO,CAAkOtD,EAAED,GAAG2D,EAAE1D,EAAED,EAAG,CAAC,EAAC4D,EAAE,SAAS5D,GAAG,CAAC,YAAY,UAAU,aAAa,eAAekE,SAAS,SAASjE,GAAG,OAAOD,EAAEC,EAAEkE,EAAEZ,EAAG,GAAG,EAACa,EAAE,SAASlE,EAAEgC,GAAG,IAAIC,EAAEC,EAAEE,IAAIG,EAAErC,EAAE,OAAO6C,EAAE,SAASjD,GAAGA,EAAE2C,UAAUP,EAAEI,kBAAkBC,EAAEnC,MAAMN,EAAEiE,gBAAgBjE,EAAE2C,UAAUF,EAAEjC,QAAQoC,KAAK5C,GAAGmC,GAAE,GAAK,EAACe,EAAElC,EAAE,cAAciC,GAAGd,EAAEF,EAAE/B,EAAEuC,EAAEP,GAAGgB,GAAGxB,GAAG,WAAWwB,EAAEI,cAAchC,IAAI2B,GAAGC,EAAER,YAAa,IAAE,GAAIQ,GAAGnB,GAAG,WAAW,IAAIf,EAAEyB,EAAErC,EAAE,OAAO+B,EAAEF,EAAE/B,EAAEuC,EAAEP,GAAG/B,EAAE,GAAGF,GAAG,EAAED,EAAE,KAAK4D,EAAE9B,kBAAkBd,EAAEiC,EAAE9C,EAAEyC,KAAK5B,GAAG6C,GAAI,GAAG,EAACQ,EAAE,CAAC,EAAEC,EAAE,SAAStE,EAAEC,GAAG,IAAIC,EAAEC,EAAEmC,IAAIJ,EAAE9B,EAAE,OAAO+B,EAAE,SAASnC,GAAG,IAAIC,EAAED,EAAE2C,UAAU1C,EAAEE,EAAEqC,kBAAkBN,EAAE5B,MAAML,EAAEiC,EAAE1B,QAAQoC,KAAK5C,GAAGE,IAAK,EAACkC,EAAEpB,EAAE,2BAA2BmB,GAAG,GAAGC,EAAE,CAAClC,EAAE+B,EAAEjC,EAAEkC,EAAEjC,GAAG,IAAIwC,EAAE,WAAW4B,EAAEnC,EAAEzB,MAAM2B,EAAEkB,cAAchC,IAAIa,GAAGC,EAAEM,aAAa2B,EAAEnC,EAAEzB,KAAI,EAAGP,GAAE,GAAK,EAAC,CAAC,UAAU,SAASgE,SAAS,SAASlE,GAAG8B,iBAAiB9B,EAAEyC,EAAE,CAAC8B,MAAK,EAAGd,SAAQ,GAAK,IAAG/B,EAAEe,GAAE,GAAIV,GAAG,SAAS5B,GAAG+B,EAAE9B,EAAE,OAAOF,EAAE+B,EAAEjC,EAAEkC,EAAEjC,GAAG+C,uBAAuB,WAAWA,uBAAuB,WAAWd,EAAE5B,MAAMwC,YAAYlC,MAAMT,EAAEkC,UAAUgC,EAAEnC,EAAEzB,KAAI,EAAGP,GAAE,EAAI,GAAG,GAAG,GAAG,CAAC,EAACsE,EAAE,SAASxE,GAAG,IAAIC,EAAEC,EAAEE,EAAE,QAAQH,EAAE,WAAW,IAAI,IAAIA,EAAE6C,YAAY2B,iBAAiB,cAAc,IAAI,WAAW,IAAIzE,EAAE8C,YAAY4B,OAAOzE,EAAE,CAAC6D,UAAU,aAAanB,UAAU,GAAG,IAAI,IAAIzC,KAAKF,EAAE,oBAAoBE,GAAG,WAAWA,IAAID,EAAEC,GAAGW,KAAK8D,IAAI3E,EAAEE,GAAGF,EAAE4E,gBAAgB,IAAI,OAAO3E,CAAE,CAAlL,GAAqL,GAAGC,EAAEI,MAAMJ,EAAEK,MAAMN,EAAE4E,cAAc3E,EAAEI,MAAM,GAAGJ,EAAEI,MAAMwC,YAAYlC,MAAM,OAAOV,EAAEM,QAAQ,CAACP,GAAGD,EAAEE,EAAa,CAAV,MAAMF,GAAI,CAAC,EAAC,aAAa2B,SAASmD,WAAWvC,WAAWtC,EAAE,GAAG6B,iBAAiB,QAAQ,WAAW,OAAOS,WAAWtC,EAAE,EAAG,GAAG,C","sources":["../node_modules/web-vitals/dist/web-vitals.js"],"sourcesContent":["var e,t,n,i,r=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:\"v2-\".concat(Date.now(),\"-\").concat(Math.floor(8999999999999*Math.random())+1e12)}},a=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if(\"first-input\"===e&&!(\"PerformanceEventTiming\"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},o=function(e,t){var n=function n(i){\"pagehide\"!==i.type&&\"hidden\"!==document.visibilityState||(e(i),t&&(removeEventListener(\"visibilitychange\",n,!0),removeEventListener(\"pagehide\",n,!0)))};addEventListener(\"visibilitychange\",n,!0),addEventListener(\"pagehide\",n,!0)},u=function(e){addEventListener(\"pageshow\",(function(t){t.persisted&&e(t)}),!0)},c=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},f=-1,s=function(){return\"hidden\"===document.visibilityState?0:1/0},m=function(){o((function(e){var t=e.timeStamp;f=t}),!0)},v=function(){return f<0&&(f=s(),m(),u((function(){setTimeout((function(){f=s(),m()}),0)}))),{get firstHiddenTime(){return f}}},d=function(e,t){var n,i=v(),o=r(\"FCP\"),f=function(e){\"first-contentful-paint\"===e.name&&(m&&m.disconnect(),e.startTime-1&&e(t)},f=r(\"CLS\",0),s=0,m=[],v=function(e){if(!e.hadRecentInput){var t=m[0],i=m[m.length-1];s&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(s+=e.value,m.push(e)):(s=e.value,m=[e]),s>f.value&&(f.value=s,f.entries=m,n())}},h=a(\"layout-shift\",v);h&&(n=c(i,f,t),o((function(){h.takeRecords().map(v),n(!0)})),u((function(){s=0,l=-1,f=r(\"CLS\",0),n=c(i,f,t)})))},T={passive:!0,capture:!0},y=new Date,g=function(i,r){e||(e=r,t=i,n=new Date,w(removeEventListener),E())},E=function(){if(t>=0&&t1e12?new Date:performance.now())-e.timeStamp;\"pointerdown\"==e.type?function(e,t){var n=function(){g(e,t),r()},i=function(){r()},r=function(){removeEventListener(\"pointerup\",n,T),removeEventListener(\"pointercancel\",i,T)};addEventListener(\"pointerup\",n,T),addEventListener(\"pointercancel\",i,T)}(t,e):g(t,e)}},w=function(e){[\"mousedown\",\"keydown\",\"touchstart\",\"pointerdown\"].forEach((function(t){return e(t,S,T)}))},L=function(n,f){var s,m=v(),d=r(\"FID\"),p=function(e){e.startTimeperformance.now())return;n.entries=[t],e(n)}catch(e){}},\"complete\"===document.readyState?setTimeout(t,0):addEventListener(\"load\",(function(){return setTimeout(t,0)}))};export{h as getCLS,d as getFCP,L as getFID,F as getLCP,P as getTTFB};\n"],"names":["e","t","n","i","r","name","value","delta","entries","id","concat","Date","now","Math","floor","random","a","PerformanceObserver","supportedEntryTypes","includes","self","getEntries","map","observe","type","buffered","o","document","visibilityState","removeEventListener","addEventListener","u","persisted","c","f","s","m","timeStamp","v","setTimeout","firstHiddenTime","d","disconnect","startTime","push","window","performance","getEntriesByName","requestAnimationFrame","p","l","h","hadRecentInput","length","takeRecords","T","passive","capture","y","g","w","E","entryType","target","cancelable","processingStart","forEach","S","L","b","F","once","P","getEntriesByType","timing","max","navigationStart","responseStart","readyState"],"sourceRoot":""} -------------------------------------------------------------------------------- /deployment/frontend/static/js/main.3336631e.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | /*! 7 | * JavaScript Cookie v2.2.1 8 | * https://github.com/js-cookie/js-cookie 9 | * 10 | * Copyright 2006, 2015 Klaus Hartl & Fagner Brack 11 | * Released under the MIT license 12 | */ 13 | 14 | /*! 15 | * The buffer module from node.js, for the browser. 16 | * 17 | * @author Feross Aboukhadijeh 18 | * @license MIT 19 | */ 20 | 21 | /*! 22 | * cookie 23 | * Copyright(c) 2012-2014 Roman Shtylman 24 | * Copyright(c) 2015 Douglas Christopher Wilson 25 | * MIT Licensed 26 | */ 27 | 28 | /*! ***************************************************************************** 29 | Copyright (c) Microsoft Corporation. 30 | 31 | Permission to use, copy, modify, and/or distribute this software for any 32 | purpose with or without fee is hereby granted. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 35 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 36 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 37 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 38 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 39 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 40 | PERFORMANCE OF THIS SOFTWARE. 41 | ***************************************************************************** */ 42 | 43 | /*! https://mths.be/punycode v1.3.2 by @mathias */ 44 | 45 | /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ 46 | 47 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 48 | 49 | /** 50 | * @license React 51 | * react-dom.production.min.js 52 | * 53 | * Copyright (c) Facebook, Inc. and its affiliates. 54 | * 55 | * This source code is licensed under the MIT license found in the 56 | * LICENSE file in the root directory of this source tree. 57 | */ 58 | 59 | /** 60 | * @license React 61 | * react-jsx-runtime.production.min.js 62 | * 63 | * Copyright (c) Facebook, Inc. and its affiliates. 64 | * 65 | * This source code is licensed under the MIT license found in the 66 | * LICENSE file in the root directory of this source tree. 67 | */ 68 | 69 | /** 70 | * @license React 71 | * react.production.min.js 72 | * 73 | * Copyright (c) Facebook, Inc. and its affiliates. 74 | * 75 | * This source code is licensed under the MIT license found in the 76 | * LICENSE file in the root directory of this source tree. 77 | */ 78 | 79 | /** 80 | * @license React 81 | * scheduler.production.min.js 82 | * 83 | * Copyright (c) Facebook, Inc. and its affiliates. 84 | * 85 | * This source code is licensed under the MIT license found in the 86 | * LICENSE file in the root directory of this source tree. 87 | */ 88 | 89 | /** 90 | * @remix-run/router v1.0.2 91 | * 92 | * Copyright (c) Remix Software Inc. 93 | * 94 | * This source code is licensed under the MIT license found in the 95 | * LICENSE.md file in the root directory of this source tree. 96 | * 97 | * @license MIT 98 | */ 99 | 100 | /** 101 | * React Router v6.4.2 102 | * 103 | * Copyright (c) Remix Software Inc. 104 | * 105 | * This source code is licensed under the MIT license found in the 106 | * LICENSE.md file in the root directory of this source tree. 107 | * 108 | * @license MIT 109 | */ 110 | 111 | /** @license React v16.13.1 112 | * react-is.production.min.js 113 | * 114 | * Copyright (c) Facebook, Inc. and its affiliates. 115 | * 116 | * This source code is licensed under the MIT license found in the 117 | * LICENSE file in the root directory of this source tree. 118 | */ 119 | -------------------------------------------------------------------------------- /deployment/frontend/static/media/ico_connect.c4b4b06e46441b63ec178326f8a9e34e.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /deployment/uuid-layer.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/deployment/uuid-layer.zip -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Amazon Connect Post-Contact Survey Solution Documentation 2 | 3 | Welcome to the comprehensive documentation for the Amazon Connect Post-Contact Survey Solution. This documentation provides detailed information about the architecture, components, and usage of the solution. 4 | 5 | ## Documentation Structure 6 | 7 | - **[Architecture](./architecture/README.md)**: System design, component relationships, and data flows 8 | - **[Frontend](./frontend/README.md)**: React application structure, components, and state management 9 | - **[Backend](./backend/README.md)**: Lambda functions, DynamoDB tables, and AWS service integrations 10 | - **[API](./api/README.md)**: API specifications, endpoints, and usage examples 11 | - **[Deployment](./deployment/README.md)**: Deployment guides, prerequisites, and configuration 12 | - **[User Guides](./user-guides/README.md)**: End-user and administrator guides 13 | 14 | ## Solution Overview 15 | 16 | The Amazon Connect Post-Contact Survey Solution enables organizations to gather customer feedback through automated surveys after contact center interactions. The solution supports custom survey configurations and automated task creation based on response thresholds. 17 | 18 | ### Key Features 19 | 20 | - **Customizable Surveys**: Create and manage surveys with configurable questions and rating scales 21 | - **Multi-Channel Support**: Collect feedback via voice (DTMF) and chat interactions 22 | - **Automated Task Creation**: Generate tasks for agents when survey responses meet specific criteria 23 | - **Secure Authentication**: Admin interface protected by Amazon Cognito 24 | - **Comprehensive Reporting**: View and analyze survey results through the admin interface 25 | 26 | ### Technology Stack 27 | 28 | - **Frontend**: React.js 29 | - **Backend**: AWS Lambda (Node.js) 30 | - **Database**: Amazon DynamoDB 31 | - **Authentication**: Amazon Cognito 32 | - **Contact Center**: Amazon Connect 33 | - **API**: Amazon API Gateway 34 | - **Content Delivery**: Amazon CloudFront 35 | - **Storage**: Amazon S3 36 | 37 | For a quick start guide, see the [Deployment Guide](./deployment/README.md). 38 | -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | # API Documentation 2 | 3 | The Amazon Connect Post-Contact Survey Solution provides a RESTful API for managing surveys and retrieving results. This document provides detailed information about the API endpoints, request/response formats, and authentication requirements. 4 | 5 | ## API Overview 6 | 7 | The API is built using Amazon API Gateway and AWS Lambda. It provides the following endpoints: 8 | 9 | - `/surveys`: Manages survey configurations 10 | - `/results`: Retrieves survey results 11 | 12 | ## Authentication 13 | 14 | All API endpoints require authentication using Amazon Cognito. The API uses the `COGNITO_USER_POOLS` authorization type with a Cognito User Pool Authorizer. 15 | 16 | ### Authentication Headers 17 | 18 | ``` 19 | Authorization: Bearer 20 | ``` 21 | 22 | ### Obtaining a JWT Token 23 | 24 | 1. Authenticate with Amazon Cognito using the user credentials 25 | 2. Receive a JWT token in the response 26 | 3. Include the token in the `Authorization` header of API requests 27 | 28 | ## API Endpoints 29 | 30 | ### Survey Management 31 | 32 | #### List Surveys 33 | 34 | Retrieves all available surveys. 35 | 36 | **Request:** 37 | - **Method:** POST 38 | - **Endpoint:** `/surveys` 39 | - **Headers:** 40 | - `Content-Type: application/json` 41 | - `Authorization: Bearer ` 42 | - **Body:** 43 | ```json 44 | { 45 | "operation": "list" 46 | } 47 | ``` 48 | 49 | **Response:** 50 | - **Status Code:** 200 OK 51 | - **Body:** 52 | ```json 53 | { 54 | "success": true, 55 | "data": [ 56 | { 57 | "surveyId": "550e8400-e29b-41d4-a716-446655440000", 58 | "surveyName": "Customer Satisfaction", 59 | "min": 1, 60 | "max": 5, 61 | "introPrompt": "Please rate your experience", 62 | "outroPrompt": "Thank you for your feedback", 63 | "question_1": "How satisfied were you with our service?", 64 | "question_2": "How likely are you to recommend us?", 65 | "flag_question_1": 3, 66 | "flag_question_2": 3 67 | } 68 | ] 69 | } 70 | ``` 71 | 72 | #### Create Survey 73 | 74 | Creates a new survey. 75 | 76 | **Request:** 77 | - **Method:** POST 78 | - **Endpoint:** `/surveys` 79 | - **Headers:** 80 | - `Content-Type: application/json` 81 | - `Authorization: Bearer ` 82 | - **Body:** 83 | ```json 84 | { 85 | "operation": "create", 86 | "data": { 87 | "surveyName": "Customer Satisfaction", 88 | "min": 1, 89 | "max": 5, 90 | "introPrompt": "Please rate your experience", 91 | "outroPrompt": "Thank you for your feedback", 92 | "questions": [ 93 | "How satisfied were you with our service?", 94 | "How likely are you to recommend us?" 95 | ], 96 | "flags": [3, 3] 97 | } 98 | } 99 | ``` 100 | 101 | **Response:** 102 | - **Status Code:** 200 OK 103 | - **Body:** 104 | ```json 105 | { 106 | "success": true, 107 | "data": "550e8400-e29b-41d4-a716-446655440000" 108 | } 109 | ``` 110 | 111 | #### Update Survey 112 | 113 | Updates an existing survey. 114 | 115 | **Request:** 116 | - **Method:** POST 117 | - **Endpoint:** `/surveys` 118 | - **Headers:** 119 | - `Content-Type: application/json` 120 | - `Authorization: Bearer ` 121 | - **Body:** 122 | ```json 123 | { 124 | "operation": "create", 125 | "data": { 126 | "surveyId": "550e8400-e29b-41d4-a716-446655440000", 127 | "surveyName": "Customer Satisfaction", 128 | "min": 1, 129 | "max": 5, 130 | "introPrompt": "Please rate your experience", 131 | "outroPrompt": "Thank you for your feedback", 132 | "questions": [ 133 | "How satisfied were you with our service?", 134 | "How likely are you to recommend us?", 135 | "How would you rate our response time?" 136 | ], 137 | "flags": [3, 3, 3] 138 | } 139 | } 140 | ``` 141 | 142 | **Response:** 143 | - **Status Code:** 200 OK 144 | - **Body:** 145 | ```json 146 | { 147 | "success": true, 148 | "data": "550e8400-e29b-41d4-a716-446655440000" 149 | } 150 | ``` 151 | 152 | #### Delete Survey 153 | 154 | Deletes an existing survey. 155 | 156 | **Request:** 157 | - **Method:** POST 158 | - **Endpoint:** `/surveys` 159 | - **Headers:** 160 | - `Content-Type: application/json` 161 | - `Authorization: Bearer ` 162 | - **Body:** 163 | ```json 164 | { 165 | "operation": "delete", 166 | "data": { 167 | "surveyId": "550e8400-e29b-41d4-a716-446655440000" 168 | } 169 | } 170 | ``` 171 | 172 | **Response:** 173 | - **Status Code:** 200 OK 174 | - **Body:** 175 | ```json 176 | { 177 | "success": true 178 | } 179 | ``` 180 | 181 | ### Survey Results 182 | 183 | #### Get Survey Results 184 | 185 | Retrieves results for a specific survey. 186 | 187 | **Request:** 188 | - **Method:** POST 189 | - **Endpoint:** `/results` 190 | - **Headers:** 191 | - `Content-Type: application/json` 192 | - `Authorization: Bearer ` 193 | - **Body:** 194 | ```json 195 | { 196 | "operation": "results", 197 | "data": { 198 | "surveyId": "550e8400-e29b-41d4-a716-446655440000" 199 | } 200 | } 201 | ``` 202 | 203 | **Response:** 204 | - **Status Code:** 200 OK 205 | - **Body:** 206 | ```json 207 | { 208 | "success": true, 209 | "data": [ 210 | { 211 | "contactId": "12345678-1234-1234-1234-123456789012", 212 | "surveyId": "550e8400-e29b-41d4-a716-446655440000", 213 | "survey_result_1": "4", 214 | "survey_result_2": "5", 215 | "timestamp": 1647532800 216 | }, 217 | { 218 | "contactId": "87654321-4321-4321-4321-210987654321", 219 | "surveyId": "550e8400-e29b-41d4-a716-446655440000", 220 | "survey_result_1": "3", 221 | "survey_result_2": "4", 222 | "timestamp": 1647533800 223 | } 224 | ] 225 | } 226 | ``` 227 | 228 | ## Error Handling 229 | 230 | ### Authentication Errors 231 | 232 | - **Status Code:** 401 Unauthorized 233 | - **Body:** 234 | ```json 235 | { 236 | "message": "Unauthorized" 237 | } 238 | ``` 239 | 240 | ### Validation Errors 241 | 242 | - **Status Code:** 400 Bad Request 243 | - **Body:** 244 | ```json 245 | { 246 | "success": false, 247 | "message": "Unsupported operation" 248 | } 249 | ``` 250 | 251 | ### Server Errors 252 | 253 | - **Status Code:** 500 Internal Server Error 254 | - **Body:** 255 | ```json 256 | { 257 | "success": false, 258 | "message": "Something went terribly wrong." 259 | } 260 | ``` 261 | 262 | ## API Gateway Configuration 263 | 264 | The API Gateway is configured with the following settings: 265 | 266 | - **Stage:** `dev` 267 | - **Logging Level:** `ERROR` 268 | - **CORS:** Enabled 269 | - **Authorization:** Cognito User Pools 270 | - **Integration Type:** Lambda Proxy 271 | 272 | ## Example API Usage 273 | 274 | ### Using cURL 275 | 276 | ```bash 277 | # Get JWT token 278 | TOKEN=$(curl -X POST \ 279 | -H "Content-Type: application/json" \ 280 | -d '{"AuthParameters":{"USERNAME":"admin","PASSWORD":"password"},"AuthFlow":"USER_PASSWORD_AUTH","ClientId":"your-client-id"}' \ 281 | https://cognito-idp.us-west-2.amazonaws.com/ | jq -r '.AuthenticationResult.IdToken') 282 | 283 | # List surveys 284 | curl -X POST \ 285 | -H "Content-Type: application/json" \ 286 | -H "Authorization: Bearer $TOKEN" \ 287 | -d '{"operation":"list"}' \ 288 | https://your-api-gateway-id.execute-api.us-west-2.amazonaws.com/dev/surveys 289 | ``` 290 | 291 | ### Using JavaScript 292 | 293 | ```javascript 294 | // Get JWT token 295 | const getToken = async () => { 296 | const response = await fetch('https://your-cognito-domain.auth.us-west-2.amazoncognito.com/oauth2/token', { 297 | method: 'POST', 298 | headers: { 299 | 'Content-Type': 'application/x-www-form-urlencoded' 300 | }, 301 | body: new URLSearchParams({ 302 | grant_type: 'password', 303 | client_id: 'your-client-id', 304 | username: 'admin', 305 | password: 'password' 306 | }) 307 | }); 308 | const data = await response.json(); 309 | return data.id_token; 310 | }; 311 | 312 | // List surveys 313 | const listSurveys = async (token) => { 314 | const response = await fetch('https://your-api-gateway-id.execute-api.us-west-2.amazonaws.com/dev/surveys', { 315 | method: 'POST', 316 | headers: { 317 | 'Content-Type': 'application/json', 318 | 'Authorization': `Bearer ${token}` 319 | }, 320 | body: JSON.stringify({ 321 | operation: 'list' 322 | }) 323 | }); 324 | return await response.json(); 325 | }; 326 | 327 | // Usage 328 | (async () => { 329 | const token = await getToken(); 330 | const surveys = await listSurveys(token); 331 | console.log(surveys); 332 | })(); 333 | ``` 334 | 335 | ## API Limitations 336 | 337 | - Maximum request size: 10 MB 338 | - Maximum response size: 10 MB 339 | - Rate limit: 10,000 requests per second 340 | - Timeout: 29 seconds 341 | -------------------------------------------------------------------------------- /docs/api/openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: Amazon Connect Post-Contact Survey API 4 | description: API for managing surveys and retrieving results 5 | version: 1.0.0 6 | servers: 7 | - url: https://{apiGatewayId}.execute-api.{region}.amazonaws.com/dev 8 | variables: 9 | apiGatewayId: 10 | default: your-api-gateway-id 11 | description: API Gateway ID 12 | region: 13 | default: us-west-2 14 | description: AWS Region 15 | paths: 16 | /surveys: 17 | post: 18 | summary: Manage surveys 19 | description: Create, update, list, or delete surveys 20 | security: 21 | - CognitoAuth: [] 22 | requestBody: 23 | required: true 24 | content: 25 | application/json: 26 | schema: 27 | oneOf: 28 | - $ref: '#/components/schemas/ListSurveysRequest' 29 | - $ref: '#/components/schemas/CreateSurveyRequest' 30 | - $ref: '#/components/schemas/DeleteSurveyRequest' 31 | responses: 32 | '200': 33 | description: Successful operation 34 | content: 35 | application/json: 36 | schema: 37 | oneOf: 38 | - $ref: '#/components/schemas/ListSurveysResponse' 39 | - $ref: '#/components/schemas/CreateSurveyResponse' 40 | - $ref: '#/components/schemas/DeleteSurveyResponse' 41 | '400': 42 | description: Bad request 43 | content: 44 | application/json: 45 | schema: 46 | $ref: '#/components/schemas/ErrorResponse' 47 | '401': 48 | description: Unauthorized 49 | content: 50 | application/json: 51 | schema: 52 | $ref: '#/components/schemas/ErrorResponse' 53 | '500': 54 | description: Internal server error 55 | content: 56 | application/json: 57 | schema: 58 | $ref: '#/components/schemas/ErrorResponse' 59 | options: 60 | summary: CORS support 61 | description: Enable CORS by returning correct headers 62 | responses: 63 | '200': 64 | description: CORS headers 65 | headers: 66 | Access-Control-Allow-Headers: 67 | schema: 68 | type: string 69 | Access-Control-Allow-Methods: 70 | schema: 71 | type: string 72 | Access-Control-Allow-Origin: 73 | schema: 74 | type: string 75 | /results: 76 | post: 77 | summary: Retrieve survey results 78 | description: Get results for a specific survey 79 | security: 80 | - CognitoAuth: [] 81 | requestBody: 82 | required: true 83 | content: 84 | application/json: 85 | schema: 86 | $ref: '#/components/schemas/GetResultsRequest' 87 | responses: 88 | '200': 89 | description: Successful operation 90 | content: 91 | application/json: 92 | schema: 93 | $ref: '#/components/schemas/GetResultsResponse' 94 | '400': 95 | description: Bad request 96 | content: 97 | application/json: 98 | schema: 99 | $ref: '#/components/schemas/ErrorResponse' 100 | '401': 101 | description: Unauthorized 102 | content: 103 | application/json: 104 | schema: 105 | $ref: '#/components/schemas/ErrorResponse' 106 | '500': 107 | description: Internal server error 108 | content: 109 | application/json: 110 | schema: 111 | $ref: '#/components/schemas/ErrorResponse' 112 | options: 113 | summary: CORS support 114 | description: Enable CORS by returning correct headers 115 | responses: 116 | '200': 117 | description: CORS headers 118 | headers: 119 | Access-Control-Allow-Headers: 120 | schema: 121 | type: string 122 | Access-Control-Allow-Methods: 123 | schema: 124 | type: string 125 | Access-Control-Allow-Origin: 126 | schema: 127 | type: string 128 | components: 129 | securitySchemes: 130 | CognitoAuth: 131 | type: http 132 | scheme: bearer 133 | bearerFormat: JWT 134 | schemas: 135 | ListSurveysRequest: 136 | type: object 137 | required: 138 | - operation 139 | properties: 140 | operation: 141 | type: string 142 | enum: [list] 143 | CreateSurveyRequest: 144 | type: object 145 | required: 146 | - operation 147 | - data 148 | properties: 149 | operation: 150 | type: string 151 | enum: [create] 152 | data: 153 | type: object 154 | required: 155 | - surveyName 156 | - min 157 | - max 158 | - introPrompt 159 | - outroPrompt 160 | - questions 161 | - flags 162 | properties: 163 | surveyId: 164 | type: string 165 | description: Survey ID (required for updates, omit for new surveys) 166 | surveyName: 167 | type: string 168 | description: Name of the survey 169 | min: 170 | type: integer 171 | description: Minimum rating value 172 | max: 173 | type: integer 174 | description: Maximum rating value 175 | introPrompt: 176 | type: string 177 | description: Introduction prompt 178 | outroPrompt: 179 | type: string 180 | description: Conclusion prompt 181 | questions: 182 | type: array 183 | description: Survey questions 184 | items: 185 | type: string 186 | flags: 187 | type: array 188 | description: Flag thresholds for questions 189 | items: 190 | type: integer 191 | DeleteSurveyRequest: 192 | type: object 193 | required: 194 | - operation 195 | - data 196 | properties: 197 | operation: 198 | type: string 199 | enum: [delete] 200 | data: 201 | type: object 202 | required: 203 | - surveyId 204 | properties: 205 | surveyId: 206 | type: string 207 | description: Survey ID to delete 208 | GetResultsRequest: 209 | type: object 210 | required: 211 | - operation 212 | - data 213 | properties: 214 | operation: 215 | type: string 216 | enum: [results] 217 | data: 218 | type: object 219 | required: 220 | - surveyId 221 | properties: 222 | surveyId: 223 | type: string 224 | description: Survey ID to get results for 225 | ListSurveysResponse: 226 | type: object 227 | required: 228 | - success 229 | - data 230 | properties: 231 | success: 232 | type: boolean 233 | example: true 234 | data: 235 | type: array 236 | items: 237 | type: object 238 | properties: 239 | surveyId: 240 | type: string 241 | example: 550e8400-e29b-41d4-a716-446655440000 242 | surveyName: 243 | type: string 244 | example: Customer Satisfaction 245 | min: 246 | type: integer 247 | example: 1 248 | max: 249 | type: integer 250 | example: 5 251 | introPrompt: 252 | type: string 253 | example: Please rate your experience 254 | outroPrompt: 255 | type: string 256 | example: Thank you for your feedback 257 | question_1: 258 | type: string 259 | example: How satisfied were you with our service? 260 | question_2: 261 | type: string 262 | example: How likely are you to recommend us? 263 | flag_question_1: 264 | type: integer 265 | example: 3 266 | flag_question_2: 267 | type: integer 268 | example: 3 269 | CreateSurveyResponse: 270 | type: object 271 | required: 272 | - success 273 | - data 274 | properties: 275 | success: 276 | type: boolean 277 | example: true 278 | data: 279 | type: string 280 | example: 550e8400-e29b-41d4-a716-446655440000 281 | DeleteSurveyResponse: 282 | type: object 283 | required: 284 | - success 285 | properties: 286 | success: 287 | type: boolean 288 | example: true 289 | GetResultsResponse: 290 | type: object 291 | required: 292 | - success 293 | - data 294 | properties: 295 | success: 296 | type: boolean 297 | example: true 298 | data: 299 | type: array 300 | items: 301 | type: object 302 | properties: 303 | contactId: 304 | type: string 305 | example: 12345678-1234-1234-1234-123456789012 306 | surveyId: 307 | type: string 308 | example: 550e8400-e29b-41d4-a716-446655440000 309 | survey_result_1: 310 | type: string 311 | example: "4" 312 | survey_result_2: 313 | type: string 314 | example: "5" 315 | timestamp: 316 | type: integer 317 | example: 1647532800 318 | ErrorResponse: 319 | type: object 320 | required: 321 | - success 322 | - message 323 | properties: 324 | success: 325 | type: boolean 326 | example: false 327 | message: 328 | type: string 329 | example: Something went terribly wrong. 330 | -------------------------------------------------------------------------------- /docs/architecture/README.md: -------------------------------------------------------------------------------- 1 | # Architecture Overview 2 | 3 | The Amazon Connect Post-Contact Survey Solution uses a serverless architecture built on AWS services. This document provides a comprehensive overview of the system architecture, component interactions, and data flows. 4 | 5 | ## Architecture Diagram 6 | 7 | ![Architecture Diagram](../infra.svg) 8 | 9 | ## System Components 10 | 11 | ### Amazon Connect Components 12 | - **Contact Flow Module**: Executes the survey flow after a contact ends 13 | - **Amazon Lex Bot**: Processes text responses for chat-based surveys 14 | - **Tasks**: Created automatically for flagged survey responses 15 | 16 | ### AWS Lambda Functions 17 | - **GetSurveyConfig**: Retrieves survey configuration from DynamoDB 18 | - **WriteSurveyResults**: Stores survey responses in DynamoDB 19 | - **ProcessSurveyFlags**: Evaluates responses against thresholds and creates tasks 20 | - **SurveyUtils**: Provides utility functions for survey flow management 21 | - **SurveyAPI**: Handles API requests for survey management 22 | - **CognitoValidateUser**: Validates user authentication 23 | 24 | ### Data Storage 25 | - **SurveysConfigDDBTable**: Stores survey configurations 26 | - **SurveysResultsDDBTable**: Stores survey responses 27 | 28 | ### Frontend Components 29 | - **React Application**: Admin interface for survey management 30 | - **S3 Bucket**: Hosts the static web application 31 | - **CloudFront Distribution**: Delivers the web application 32 | 33 | ### API and Authentication 34 | - **API Gateway**: Routes API requests to Lambda functions 35 | - **Cognito User Pool**: Manages user authentication 36 | 37 | ## Data Flow 38 | 39 | ### Survey Creation Flow 40 | 1. Admin authenticates through Cognito 41 | 2. Admin creates survey configuration through the web interface 42 | 3. API Gateway routes the request to the SurveyAPI Lambda 43 | 4. Lambda stores the survey configuration in DynamoDB 44 | 45 | ### Survey Execution Flow 46 | 1. Contact completes in Amazon Connect 47 | 2. Contact Flow Module is triggered 48 | 3. GetSurveyConfig Lambda retrieves survey configuration 49 | 4. Contact Flow presents questions to the customer 50 | 5. Customer responses are collected via DTMF (voice) or Lex (chat) 51 | 6. WriteSurveyResults Lambda stores responses in DynamoDB 52 | 7. ProcessSurveyFlags Lambda evaluates responses against thresholds 53 | 8. Tasks are created in Amazon Connect for flagged responses 54 | 55 | ### Survey Results Flow 56 | 1. Admin authenticates through Cognito 57 | 2. Admin views survey results through the web interface 58 | 3. API Gateway routes the request to the SurveyAPI Lambda 59 | 4. Lambda retrieves survey results from DynamoDB 60 | 61 | ## Security Architecture 62 | 63 | ### Authentication and Authorization 64 | - **Cognito User Pool**: Manages user authentication for the admin interface 65 | - **IAM Roles**: Control access to AWS resources 66 | - **API Gateway Authorizers**: Validate requests to API endpoints 67 | 68 | ### Data Protection 69 | - **S3 Bucket Encryption**: Protects static web assets 70 | - **CloudFront Distribution**: Secures content delivery 71 | - **IAM Policies**: Implement least privilege access 72 | 73 | ## Scalability Considerations 74 | 75 | The serverless architecture allows the solution to scale automatically based on demand: 76 | 77 | - **Lambda Functions**: Scale automatically with request volume 78 | - **DynamoDB Tables**: Provisioned with appropriate capacity 79 | - **CloudFront Distribution**: Handles global content delivery 80 | 81 | ## Resilience and Availability 82 | 83 | - **Multi-AZ Deployment**: AWS services operate across multiple Availability Zones 84 | - **DynamoDB**: Provides high availability and durability 85 | - **CloudFront**: Ensures global availability of the web application 86 | 87 | ## Integration Points 88 | 89 | - **Amazon Connect**: Integration through Contact Flow Module and Lambda functions 90 | - **API Gateway**: Provides RESTful API endpoints for the web application 91 | - **Cognito**: Handles user authentication and authorization 92 | -------------------------------------------------------------------------------- /docs/architecture/component-diagram.md: -------------------------------------------------------------------------------- 1 | # Component Diagram 2 | 3 | ``` 4 | ┌───────────────────────────────────────────────────────────────────────────────────────────────────────┐ 5 | │ │ 6 | │ Amazon Connect Instance │ 7 | │ │ 8 | │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────┐ │ 9 | │ │ │ │ │ │ │ │ │ │ 10 | │ │ Contact Flow │ │ Contact Flow │ │ Contact Flow │ │ Contact Flow Module │ │ 11 | │ │ │ │ │ │ │ │ (Survey Flow) │ │ 12 | │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ └───────────┬───────────┘ │ 13 | │ │ │ │ │ │ 14 | └───────────┼──────────────────────┼──────────────────────┼──────────────────────────┼────────────────┘ 15 | │ │ │ │ 16 | │ │ │ │ 17 | │ │ │ │ 18 | ┌───────────┼──────────────────────┼──────────────────────┼──────────────────────────┼────────────────┐ 19 | │ │ │ │ │ │ 20 | │ │ │ │ ▼ │ 21 | │ │ │ │ ┌─────────────────────────┐ │ 22 | │ │ │ │ │ │ │ 23 | │ │ │ │ │ Amazon Lex Bot │ │ 24 | │ │ │ │ │ (Survey Responses) │ │ 25 | │ │ │ │ │ │ │ 26 | │ │ │ │ └─────────────────────────┘ │ 27 | │ │ │ │ │ 28 | └───────────┼──────────────────────┼──────────────────────┼───────────────────────────────────────────┘ 29 | │ │ │ 30 | │ │ │ 31 | ▼ ▼ ▼ 32 | ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ 33 | │ │ 34 | │ AWS Lambda Functions │ 35 | │ │ 36 | │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 37 | │ │ │ │ │ │ │ │ │ │ 38 | │ │ GetSurveyConfig │ │ WriteSurvey │ │ ProcessSurvey │ │ SurveyUtils │ │ 39 | │ │ │ │ Results │ │ Flags │ │ │ │ 40 | │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ 41 | │ │ │ │ │ │ 42 | │ │ │ │ │ │ 43 | │ ▼ ▼ ▼ │ │ 44 | │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ 45 | │ │ │ │ │ 46 | │ │ DynamoDB Tables │ │ │ 47 | │ │ │ │ │ 48 | │ │ ┌─────────────────────┐ ┌─────────────────────────┐ │ │ │ 49 | │ │ │ │ │ │ │ │ │ 50 | │ │ │ SurveysConfigTable │◄───────────┤ SurveysResultsTable │ │ │ │ 51 | │ │ │ │ │ │ │ │ │ 52 | │ │ └─────────────────────┘ └─────────────────────────┘ │ │ │ 53 | │ │ │ │ │ 54 | │ └─────────────────────────────────────────────────────────────────────────┘ │ │ 55 | │ │ │ 56 | └─────────────────────────────────────────────────────────────────────────────────┼───────────────────┘ 57 | │ 58 | │ 59 | ┌─────────────────────────────────────────────────────────────────────────────────┼───────────────────┐ 60 | │ │ │ 61 | │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ 62 | │ │ │ │ │ 63 | │ │ API Gateway │ │ │ 64 | │ │ │ │ │ 65 | │ │ ┌─────────────────────┐ ┌─────────────────────────┐ │ │ │ 66 | │ │ │ │ │ │ │ │ │ 67 | │ │ │ /surveys endpoint │ │ /results endpoint │ │ │ │ 68 | │ │ │ │ │ │ │ │ │ 69 | │ │ └──────────┬──────────┘ └────────────┬────────────┘ │ │ │ 70 | │ │ │ │ │ │ │ 71 | │ └─────────────┼────────────────────────────────────┼──────────────────┘ │ │ 72 | │ │ │ │ │ 73 | │ │ │ │ │ 74 | │ │ │ │ │ 75 | │ ▼ ▼ │ │ 76 | │ ┌─────────────────────────────────────────────────────────────────┐ │ │ 77 | │ │ │ │ │ 78 | │ │ Lambda API Function │ │ │ 79 | │ │ │ │ │ 80 | │ └─────────────────────────────────────────────────────────────────┘ │ │ 81 | │ │ │ 82 | └─────────────────────────────────────────────────────────────────────────────────┼───────────────────┘ 83 | │ 84 | │ 85 | ┌─────────────────────────────────────────────────────────────────────────────────┼───────────────────┐ 86 | │ │ │ 87 | │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ 88 | │ │ │ │ │ 89 | │ │ CloudFront Distribution │◄───────┘ │ 90 | │ │ │ │ 91 | │ └────────────────────────────────┬────────────────────────────────────┘ │ 92 | │ │ │ 93 | │ │ │ 94 | │ ▼ │ 95 | │ ┌─────────────────────────────────────────────────────────────────────┐ │ 96 | │ │ │ │ 97 | │ │ S3 Frontend Bucket │ │ 98 | │ │ │ │ 99 | │ └─────────────────────────────────────────────────────────────────────┘ │ 100 | │ │ 101 | └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ 102 | 103 | ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐ 104 | │ │ 105 | │ Amazon Cognito │ 106 | │ │ 107 | │ ┌─────────────────────┐ ┌─────────────────┐ │ 108 | │ │ │ │ │ │ 109 | │ │ User Pool │ │ User Pool │ │ 110 | │ │ │ │ Client │ │ 111 | │ └─────────────────────┘ └─────────────────┘ │ 112 | │ │ 113 | └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ 114 | ``` 115 | -------------------------------------------------------------------------------- /docs/architecture/data-flow.md: -------------------------------------------------------------------------------- 1 | # Data Flow Diagrams 2 | 3 | This document provides detailed data flow diagrams for the key processes in the Amazon Connect Post-Contact Survey Solution. 4 | 5 | ## Survey Creation Flow 6 | 7 | ``` 8 | ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ 9 | │ │ │ │ │ │ │ │ 10 | │ Admin │────►│ Frontend │────►│ API Gateway │────►│ Lambda API │ 11 | │ │ │ │ │ │ │ │ 12 | └─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘ 13 | │ 14 | ▼ 15 | ┌─────────────┐ 16 | │ │ 17 | │ DynamoDB │ 18 | │ Config │ 19 | │ │ 20 | └─────────────┘ 21 | ``` 22 | 23 | 1. Admin creates or updates a survey through the frontend interface 24 | 2. Frontend sends a request to API Gateway 25 | 3. API Gateway routes the request to the Lambda API function 26 | 4. Lambda API function stores the survey configuration in DynamoDB 27 | 28 | ## Survey Execution Flow 29 | 30 | ``` 31 | ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ 32 | │ │ │ │ │ │ │ │ 33 | │ Contact │────►│ Connect │────►│ Contact Flow│────►│ GetSurvey │ 34 | │ Ends │ │ Instance │ │ Module │ │ Config │ 35 | │ │ │ │ │ │ │ │ 36 | └─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘ 37 | │ 38 | ▼ 39 | ┌─────────────┐ 40 | │ │ 41 | │ DynamoDB │ 42 | │ Config │ 43 | │ │ 44 | └──────┬──────┘ 45 | │ 46 | ▼ 47 | ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ 48 | │ │ │ │ │ │ │ │ 49 | │ Customer │◄────│ Survey │◄────│ Survey │◄────│ Survey │ 50 | │ │ │ Questions │ │ Utils │ │ Config │ 51 | │ │ │ │ │ │ │ │ 52 | └──────┬──────┘ └─────────────┘ └─────────────┘ └─────────────┘ 53 | │ 54 | ▼ 55 | ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ 56 | │ │ │ │ │ │ 57 | │ Customer │────►│ Write Survey│────►│ DynamoDB │ 58 | │ Responses │ │ Results │ │ Results │ 59 | │ │ │ │ │ │ 60 | └─────────────┘ └──────┬──────┘ └─────────────┘ 61 | │ 62 | ▼ 63 | ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ 64 | │ │ │ │ │ │ 65 | │ Process │────►│ Create │────►│ Connect │ 66 | │ Survey Flags│ │ Task │ │ Task │ 67 | │ │ │ │ │ │ 68 | └─────────────┘ └─────────────┘ └─────────────┘ 69 | ``` 70 | 71 | 1. Contact ends in Amazon Connect 72 | 2. Contact Flow Module is triggered to start the survey 73 | 3. GetSurveyConfig Lambda retrieves survey configuration from DynamoDB 74 | 4. Survey questions are presented to the customer 75 | 5. Customer responses are collected 76 | 6. WriteSurveyResults Lambda stores responses in DynamoDB 77 | 7. ProcessSurveyFlags Lambda evaluates responses against thresholds 78 | 8. Tasks are created in Amazon Connect for flagged responses 79 | 80 | ## Survey Results Retrieval Flow 81 | 82 | ``` 83 | ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ 84 | │ │ │ │ │ │ │ │ 85 | │ Admin │────►│ Frontend │────►│ API Gateway │────►│ Lambda API │ 86 | │ │ │ │ │ │ │ │ 87 | └─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘ 88 | │ 89 | ▼ 90 | ┌─────────────┐ 91 | │ │ 92 | │ DynamoDB │ 93 | │ Results │ 94 | │ │ 95 | └──────┬──────┘ 96 | │ 97 | ▼ 98 | ┌─────────────┐ 99 | │ │ 100 | │ Frontend │ 101 | │ Display │ 102 | │ │ 103 | └─────────────┘ 104 | ``` 105 | 106 | 1. Admin requests survey results through the frontend interface 107 | 2. Frontend sends a request to API Gateway 108 | 3. API Gateway routes the request to the Lambda API function 109 | 4. Lambda API function retrieves survey results from DynamoDB 110 | 5. Results are displayed in the frontend interface 111 | 112 | ## Authentication Flow 113 | 114 | ``` 115 | ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ 116 | │ │ │ │ │ │ 117 | │ User │────►│ Frontend │────►│ Cognito │ 118 | │ │ │ │ │ User Pool │ 119 | └─────────────┘ └─────────────┘ └──────┬──────┘ 120 | │ 121 | ▼ 122 | ┌─────────────┐ 123 | │ │ 124 | │ JWT Token │ 125 | │ │ 126 | └──────┬──────┘ 127 | │ 128 | ▼ 129 | ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ 130 | │ │ │ │ │ │ 131 | │ Protected │◄────│ API Gateway │◄────│ Token │ 132 | │ Resources │ │ Authorizer │ │ Validation │ 133 | │ │ │ │ │ │ 134 | └─────────────┘ └─────────────┘ └─────────────┘ 135 | ``` 136 | 137 | 1. User logs in through the frontend interface 138 | 2. Frontend authenticates with Cognito User Pool 139 | 3. Cognito returns a JWT token 140 | 4. Token is included in API requests 141 | 5. API Gateway Authorizer validates the token 142 | 6. User accesses protected resources 143 | -------------------------------------------------------------------------------- /docs/backend/README.md: -------------------------------------------------------------------------------- 1 | # Backend Documentation 2 | 3 | The backend of the Amazon Connect Post-Contact Survey Solution is built using AWS Lambda functions, DynamoDB tables, and other AWS services. This document provides detailed information about the backend components and their interactions. 4 | 5 | ## Lambda Functions 6 | 7 | ### API Lambda Function 8 | 9 | The API Lambda function handles API requests from the frontend application. 10 | 11 | **File:** `lambdas/api/index.js` 12 | 13 | **Purpose:** 14 | - Processes API requests for survey operations 15 | - Handles CRUD operations for surveys 16 | - Retrieves survey results 17 | 18 | **Operations:** 19 | - `list`: Retrieves all surveys 20 | - `create`: Creates a new survey 21 | - `update`: Updates an existing survey 22 | - `delete`: Deletes a survey 23 | - `results`: Retrieves survey results 24 | 25 | **Environment Variables:** 26 | - `TABLE_SURVEYS_CONFIG`: Name of the DynamoDB table for survey configurations 27 | - `TABLE_SURVEYS_RESULTS`: Name of the DynamoDB table for survey results 28 | 29 | **Example Request:** 30 | ```json 31 | { 32 | "operation": "create", 33 | "data": { 34 | "surveyName": "Customer Satisfaction", 35 | "min": 1, 36 | "max": 5, 37 | "introPrompt": "Please rate your experience", 38 | "outroPrompt": "Thank you for your feedback", 39 | "questions": [ 40 | "How satisfied were you with our service?", 41 | "How likely are you to recommend us?" 42 | ], 43 | "flags": [3, 3] 44 | } 45 | } 46 | ``` 47 | 48 | **Example Response:** 49 | ```json 50 | { 51 | "success": true, 52 | "data": "550e8400-e29b-41d4-a716-446655440000" 53 | } 54 | ``` 55 | 56 | ### GetSurveyConfig Lambda Function 57 | 58 | Retrieves survey configuration from DynamoDB for use in Amazon Connect contact flows. 59 | 60 | **File:** `lambdas/getSurveyConfig/index.js` 61 | 62 | **Purpose:** 63 | - Retrieves survey configuration from DynamoDB 64 | - Formats survey data for use in Amazon Connect 65 | 66 | **Environment Variables:** 67 | - `TABLE`: Name of the DynamoDB table for survey configurations 68 | 69 | **Example Request:** 70 | ```json 71 | { 72 | "Details": { 73 | "Parameters": { 74 | "surveyId": "550e8400-e29b-41d4-a716-446655440000" 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | **Example Response:** 81 | ```json 82 | { 83 | "statusCode": 200, 84 | "message": "OK", 85 | "surveyId": "550e8400-e29b-41d4-a716-446655440000", 86 | "surveyName": "Customer Satisfaction", 87 | "min": 1, 88 | "max": 5, 89 | "introPrompt": "Please rate your experience", 90 | "outroPrompt": "Thank you for your feedback", 91 | "question_1": "How satisfied were you with our service?", 92 | "question_2": "How likely are you to recommend us?", 93 | "flag_question_1": 3, 94 | "flag_question_2": 3, 95 | "surveySize": 2 96 | } 97 | ``` 98 | 99 | ### WriteSurveyResults Lambda Function 100 | 101 | Stores survey responses in DynamoDB. 102 | 103 | **File:** `lambdas/writeSurveyResults/index.js` 104 | 105 | **Purpose:** 106 | - Stores survey responses in DynamoDB 107 | - Processes survey attributes from Amazon Connect 108 | 109 | **Environment Variables:** 110 | - `TABLE`: Name of the DynamoDB table for survey results 111 | 112 | **Example Request:** 113 | ```json 114 | { 115 | "Details": { 116 | "ContactData": { 117 | "ContactId": "12345678-1234-1234-1234-123456789012", 118 | "Attributes": { 119 | "surveyId": "550e8400-e29b-41d4-a716-446655440000", 120 | "survey_result_1": "4", 121 | "survey_result_2": "5" 122 | } 123 | } 124 | } 125 | } 126 | ``` 127 | 128 | **Example Response:** 129 | ```json 130 | { 131 | "statusCode": 200, 132 | "body": "OK" 133 | } 134 | ``` 135 | 136 | ### ProcessSurveyFlags Lambda Function 137 | 138 | Evaluates survey responses against thresholds and creates tasks in Amazon Connect. 139 | 140 | **File:** `lambdas/processReviewFlags/index.js` 141 | 142 | **Purpose:** 143 | - Evaluates survey responses against thresholds 144 | - Creates tasks in Amazon Connect for flagged responses 145 | 146 | **Environment Variables:** 147 | - `CONTACT_FLOW_ID`: ID of the contact flow for tasks 148 | - `INSTANCE_NAME`: Name of the Amazon Connect instance 149 | 150 | **Example Request:** 151 | ```json 152 | { 153 | "Details": { 154 | "ContactData": { 155 | "ContactId": "12345678-1234-1234-1234-123456789012", 156 | "InstanceARN": "arn:aws:connect:us-west-2:123456789012:instance/12345678-1234-1234-1234-123456789012", 157 | "Attributes": { 158 | "surveyId": "550e8400-e29b-41d4-a716-446655440000", 159 | "survey_result_1": "2", 160 | "survey_result_2": "5" 161 | } 162 | }, 163 | "Parameters": { 164 | "flag_question_1": "3", 165 | "flag_question_2": "3" 166 | } 167 | } 168 | } 169 | ``` 170 | 171 | **Example Response:** 172 | ```json 173 | { 174 | "statusCode": 200, 175 | "body": "OK" 176 | } 177 | ``` 178 | 179 | ### SurveyUtils Lambda Function 180 | 181 | Provides utility functions for survey flow management. 182 | 183 | **File:** `lambdas/surveyUtils/index.js` 184 | 185 | **Purpose:** 186 | - Manages survey flow in Amazon Connect 187 | - Validates user input 188 | - Retrieves next survey question 189 | 190 | **Operations:** 191 | - `getNextSurveyQuestion`: Gets the next question in the survey 192 | - `validateInput`: Validates user input against min/max bounds 193 | 194 | **Example Request:** 195 | ```json 196 | { 197 | "Details": { 198 | "Parameters": { 199 | "operation": "getNextSurveyQuestion", 200 | "question_1": "How satisfied were you with our service?", 201 | "question_2": "How likely are you to recommend us?" 202 | }, 203 | "ContactData": { 204 | "Attributes": { 205 | "loopCounter": "1" 206 | } 207 | } 208 | } 209 | } 210 | ``` 211 | 212 | **Example Response:** 213 | ```json 214 | { 215 | "operation": "getNextSurveyQuestion", 216 | "nextQuestion": "How satisfied were you with our service?", 217 | "newCounter": 2, 218 | "currentQuestionIndex": "1" 219 | } 220 | ``` 221 | 222 | ### CognitoValidateUser Lambda Function 223 | 224 | Validates user authentication through Amazon Cognito. 225 | 226 | **File:** `lambdas/cognitoValidateUser/index.js` 227 | 228 | **Purpose:** 229 | - Validates user authentication 230 | - Updates user attributes in Cognito 231 | 232 | **Example Request:** 233 | ```json 234 | { 235 | "ResourceProperties": { 236 | "UserPoolId": "us-west-2_abcdefghi", 237 | "Username": "admin" 238 | } 239 | } 240 | ``` 241 | 242 | **Example Response:** 243 | ```json 244 | { 245 | "statusCode": 200, 246 | "body": {} 247 | } 248 | ``` 249 | 250 | ## DynamoDB Tables 251 | 252 | ### SurveysConfigDDBTable 253 | 254 | Stores survey configurations. 255 | 256 | **Schema:** 257 | - `surveyId` (String): Primary key 258 | - `surveyName` (String): Name of the survey 259 | - `min` (Number): Minimum rating value 260 | - `max` (Number): Maximum rating value 261 | - `introPrompt` (String): Introduction prompt 262 | - `outroPrompt` (String): Conclusion prompt 263 | - `question_1`, `question_2`, etc. (String): Survey questions 264 | - `flag_question_1`, `flag_question_2`, etc. (Number): Flag thresholds 265 | 266 | **Example Item:** 267 | ```json 268 | { 269 | "surveyId": "550e8400-e29b-41d4-a716-446655440000", 270 | "surveyName": "Customer Satisfaction", 271 | "min": 1, 272 | "max": 5, 273 | "introPrompt": "Please rate your experience", 274 | "outroPrompt": "Thank you for your feedback", 275 | "question_1": "How satisfied were you with our service?", 276 | "question_2": "How likely are you to recommend us?", 277 | "flag_question_1": 3, 278 | "flag_question_2": 3 279 | } 280 | ``` 281 | 282 | ### SurveysResultsDDBTable 283 | 284 | Stores survey responses. 285 | 286 | **Schema:** 287 | - `contactId` (String): Primary key 288 | - `surveyId` (String): ID of the survey 289 | - `survey_result_1`, `survey_result_2`, etc. (String): Survey responses 290 | - `timestamp` (Number): Unix timestamp 291 | 292 | **Example Item:** 293 | ```json 294 | { 295 | "contactId": "12345678-1234-1234-1234-123456789012", 296 | "surveyId": "550e8400-e29b-41d4-a716-446655440000", 297 | "survey_result_1": "4", 298 | "survey_result_2": "5", 299 | "timestamp": 1647532800 300 | } 301 | ``` 302 | 303 | ## Amazon Connect Integration 304 | 305 | ### Contact Flow Module 306 | 307 | The Contact Flow Module executes the survey flow after a contact ends. 308 | 309 | **Key Components:** 310 | - Invokes GetSurveyConfig Lambda to retrieve survey configuration 311 | - Presents questions to the customer 312 | - Collects responses via DTMF (voice) or Lex (chat) 313 | - Invokes WriteSurveyResults Lambda to store responses 314 | - Invokes ProcessSurveyFlags Lambda to evaluate responses 315 | 316 | **Flow Logic:** 317 | 1. Retrieve survey configuration 318 | 2. Present introduction prompt 319 | 3. Loop through survey questions 320 | 4. Collect and validate responses 321 | 5. Store responses 322 | 6. Evaluate responses against thresholds 323 | 7. Present conclusion prompt 324 | 325 | ### Amazon Lex Bot 326 | 327 | The Amazon Lex Bot processes text responses for chat-based surveys. 328 | 329 | **Intents:** 330 | - `0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`: Numeric responses 331 | - `FallbackIntent`: Default intent when no other intent matches 332 | 333 | **Utterances:** 334 | - Numeric values (0-9) 335 | 336 | ## Error Handling 337 | 338 | ### Lambda Error Handling 339 | 340 | ```javascript 341 | try { 342 | // Operation code 343 | } catch (err) { 344 | console.log(err); 345 | return { 346 | statusCode: 500, 347 | body: JSON.stringify({ error: 'Internal server error' }) 348 | }; 349 | } 350 | ``` 351 | 352 | ### DynamoDB Error Handling 353 | 354 | ```javascript 355 | try { 356 | const command = new PutCommand(params); 357 | await docClient.send(command); 358 | } catch (err) { 359 | console.log(err); 360 | throw err; 361 | } 362 | ``` 363 | 364 | ### Connect Flow Error Handling 365 | 366 | The Contact Flow Module includes error handling branches for: 367 | - Lambda invocation errors 368 | - Invalid user input 369 | - Timeout errors 370 | 371 | ## Logging and Monitoring 372 | 373 | ### CloudWatch Logs 374 | 375 | All Lambda functions log to CloudWatch Logs: 376 | 377 | ```javascript 378 | console.log('Processing survey response:', event); 379 | ``` 380 | 381 | ### API Gateway Logging 382 | 383 | API Gateway is configured with ERROR level logging: 384 | 385 | ```json 386 | { 387 | "MethodSettings": [ 388 | { 389 | "DataTraceEnabled": true, 390 | "HttpMethod": "*", 391 | "LoggingLevel": "ERROR", 392 | "ResourcePath": "/*" 393 | } 394 | ] 395 | } 396 | ``` 397 | 398 | ## Security 399 | 400 | ### IAM Roles 401 | 402 | Each Lambda function has a specific IAM role with least privilege permissions: 403 | 404 | - `LambdaSurveysApiRole`: Permissions for API operations 405 | - `LambdaGetSurveyConfigRole`: Permissions to read survey configurations 406 | - `LambdaWriteSurveyResultsRole`: Permissions to write survey results 407 | - `LambdaProcessSurveysFlagsRole`: Permissions to create tasks in Connect 408 | 409 | ### API Gateway Authorization 410 | 411 | API Gateway uses Cognito User Pools for authorization: 412 | 413 | ```json 414 | { 415 | "AuthorizationType": "COGNITO_USER_POOLS", 416 | "AuthorizerId": "CognitoAuthorizer" 417 | } 418 | ``` 419 | 420 | ### DynamoDB Encryption 421 | 422 | DynamoDB tables use server-side encryption: 423 | 424 | ```json 425 | { 426 | "SSESpecification": { 427 | "Enabled": true 428 | } 429 | } 430 | ``` 431 | -------------------------------------------------------------------------------- /docs/deployment/README.md: -------------------------------------------------------------------------------- 1 | # Deployment Guide 2 | 3 | This guide provides detailed instructions for deploying the Amazon Connect Post-Contact Survey Solution. 4 | 5 | ## Prerequisites 6 | 7 | Before deploying the solution, ensure you have the following: 8 | 9 | - **AWS Account**: An AWS account with permissions to create the required resources 10 | - **Amazon Connect Instance**: An existing Amazon Connect instance 11 | - **AWS CLI**: Installed and configured with appropriate permissions 12 | - **Node.js**: Version 14.x or later 13 | - **Admin Email Address**: An email address for the initial admin user 14 | 15 | ## Required Permissions 16 | 17 | The deployment requires permissions to create and manage the following AWS resources: 18 | 19 | - Lambda functions 20 | - S3 buckets 21 | - IAM roles 22 | - Cognito user pools 23 | - DynamoDB tables 24 | - API Gateway 25 | - CloudFront distributions 26 | - Amazon Lex bots 27 | 28 | ## Deployment Steps 29 | 30 | ### 1. Clone the Repository 31 | 32 | ```bash 33 | git clone 34 | cd amazon-connect-contact-surveys 35 | ``` 36 | 37 | ### 2. Install Dependencies 38 | 39 | ```bash 40 | npm install 41 | ``` 42 | 43 | ### 3. Build the Frontend 44 | 45 | ```bash 46 | npm run build 47 | ``` 48 | 49 | ### 4. Deploy the CloudFormation Stack 50 | 51 | ```bash 52 | aws cloudformation deploy \ 53 | --template-file deployment/contact-surveys-amazon-connect.yaml \ 54 | --stack-name contact-surveys \ 55 | --capabilities CAPABILITY_IAM \ 56 | --parameter-overrides \ 57 | AmazonConnectInstanceARN= \ 58 | AmazonConnectInstanceName= \ 59 | ContactFlowIdForTasks= \ 60 | AdminEmailAddress= 61 | ``` 62 | 63 | Replace the placeholder values with your specific information: 64 | 65 | - ``: The ARN of your Amazon Connect instance 66 | - ``: The name of your Amazon Connect instance 67 | - ``: The ID of the contact flow you want generated tasks to be directed to 68 | - ``: The email address for the initial admin user 69 | 70 | ### 5. Get the CloudFormation Outputs 71 | 72 | ```bash 73 | aws cloudformation describe-stacks \ 74 | --stack-name contact-surveys \ 75 | --query "Stacks[0].Outputs" 76 | ``` 77 | 78 | Note the following outputs: 79 | - `WebClient`: The URL for accessing the frontend application 80 | - `AdminUser`: The initial admin user for the frontend 81 | 82 | ### 6. Configure Amazon Connect 83 | 84 | #### Import the Contact Flow Module 85 | 86 | 1. Log in to the Amazon Connect admin console 87 | 2. Navigate to "Routing" > "Contact flows" 88 | 3. Click "Create contact flow" > "Import flow" 89 | 4. Upload the contact flow module from the CloudFormation outputs 90 | 91 | #### Configure Contact Flows 92 | 93 | 1. Create or edit a contact flow that will trigger the survey 94 | 2. Add a "Transfer to flow" block 95 | 3. Select the "Contact Survey" module 96 | 4. Configure the parameters: 97 | - `surveyId`: The ID of the survey to use 98 | 99 | ### 7. Verify Deployment 100 | 101 | 1. Access the frontend application using the `WebClient` URL from the CloudFormation outputs 102 | 2. Log in using the admin credentials sent to the specified email address 103 | 3. Create a test survey 104 | 4. Test the survey flow in Amazon Connect 105 | 106 | ## Post-Deployment Configuration 107 | 108 | ### Configure Survey Triggers 109 | 110 | 1. Identify the contact flows where you want to trigger surveys 111 | 2. Add a "Set contact attributes" block to set the `surveyId` attribute 112 | 3. Add a "Transfer to flow" block to transfer to the "Contact Survey" module 113 | 114 | ### Configure Task Creation 115 | 116 | 1. Create a contact flow for handling tasks created by flagged survey responses 117 | 2. Update the `ContactFlowIdForTasks` parameter in the CloudFormation stack if needed 118 | 119 | ### Configure User Access 120 | 121 | 1. Log in to the Amazon Cognito console 122 | 2. Navigate to the user pool created by the CloudFormation stack 123 | 3. Add additional users as needed 124 | 125 | ## Troubleshooting 126 | 127 | ### Common Issues 128 | 129 | #### Cognito User Not Confirmed 130 | 131 | **Error**: "User is not confirmed" 132 | 133 | **Solution**: 134 | ```bash 135 | aws cognito-idp admin-confirm-sign-up \ 136 | --user-pool-id \ 137 | --username 138 | ``` 139 | 140 | #### Survey Creation Failures 141 | 142 | **Error**: Survey creation fails 143 | 144 | **Solution**: 145 | 1. Check Lambda CloudWatch logs: `/aws/lambda/contact-surveys-surveys-api` 146 | 2. Verify DynamoDB permissions 147 | 3. Enable debug logging: 148 | ```javascript 149 | const debugMode = true; 150 | console.debug('Survey creation payload:', surveyData); 151 | ``` 152 | 153 | #### Contact Flow Module Not Working 154 | 155 | **Error**: Survey not triggered after contact 156 | 157 | **Solution**: 158 | 1. Verify the `surveyId` attribute is set correctly 159 | 2. Check Lambda CloudWatch logs for errors 160 | 3. Verify IAM permissions for Lambda functions 161 | 162 | #### Frontend Access Issues 163 | 164 | **Error**: Unable to access the frontend application 165 | 166 | **Solution**: 167 | 1. Verify the CloudFront distribution is deployed 168 | 2. Check S3 bucket permissions 169 | 3. Verify Cognito user pool configuration 170 | 171 | ## Updating the Solution 172 | 173 | To update the solution to a new version: 174 | 175 | 1. Pull the latest changes from the repository 176 | 2. Install dependencies and build the frontend 177 | 3. Update the CloudFormation stack: 178 | 179 | ```bash 180 | aws cloudformation update-stack \ 181 | --stack-name contact-surveys \ 182 | --template-body file://deployment/contact-surveys-amazon-connect.yaml \ 183 | --capabilities CAPABILITY_IAM \ 184 | --parameters \ 185 | ParameterKey=AmazonConnectInstanceARN,UsePreviousValue=true \ 186 | ParameterKey=AmazonConnectInstanceName,UsePreviousValue=true \ 187 | ParameterKey=ContactFlowIdForTasks,UsePreviousValue=true \ 188 | ParameterKey=AdminEmailAddress,UsePreviousValue=true 189 | ``` 190 | 191 | ## Uninstalling the Solution 192 | 193 | To remove the solution: 194 | 195 | ```bash 196 | aws cloudformation delete-stack \ 197 | --stack-name contact-surveys 198 | ``` 199 | 200 | Note: This will delete all resources created by the CloudFormation stack, including the DynamoDB tables containing survey configurations and results. Make sure to back up any important data before deleting the stack. 201 | -------------------------------------------------------------------------------- /docs/deployment/cloudformation-parameters.md: -------------------------------------------------------------------------------- 1 | # CloudFormation Parameters 2 | 3 | This document provides detailed information about the CloudFormation parameters used in the Amazon Connect Post-Contact Survey Solution. 4 | 5 | ## Required Parameters 6 | 7 | ### AmazonConnectInstanceARN 8 | 9 | - **Description**: The Amazon Connect instance ARN 10 | - **Type**: String 11 | - **Format**: `arn:aws:connect:region:account-id:instance/instance-id` 12 | - **Example**: `arn:aws:connect:us-west-2:123456789012:instance/12345678-1234-1234-1234-123456789012` 13 | - **How to find it**: 14 | 1. Log in to the AWS Console 15 | 2. Navigate to Amazon Connect 16 | 3. Select your instance 17 | 4. The ARN is displayed in the instance details 18 | 19 | ### AmazonConnectInstanceName 20 | 21 | - **Description**: The Amazon Connect instance name 22 | - **Type**: String 23 | - **Format**: The name of your Amazon Connect instance 24 | - **Example**: `my-connect-instance` 25 | - **How to find it**: 26 | 1. Log in to the AWS Console 27 | 2. Navigate to Amazon Connect 28 | 3. The instance name is displayed in the instance list 29 | 30 | ### ContactFlowIdForTasks 31 | 32 | - **Description**: The contact flow you want generated tasks to be directed to 33 | - **Type**: String 34 | - **Format**: The ID of the contact flow 35 | - **Example**: `12345678-1234-1234-1234-123456789012` 36 | - **How to find it**: 37 | 1. Log in to the Amazon Connect admin console 38 | 2. Navigate to "Routing" > "Contact flows" 39 | 3. Select the contact flow 40 | 4. The ID is in the URL: `/contact-flow/edit?id=` 41 | 42 | ### AdminEmailAddress 43 | 44 | - **Description**: The email address for the initial user of the solution 45 | - **Type**: String 46 | - **Format**: A valid email address 47 | - **Example**: `admin@example.com` 48 | - **Note**: This email address will receive the initial password for the admin user 49 | 50 | ## CloudFormation Stack Outputs 51 | 52 | ### WebClient 53 | 54 | - **Description**: The frontend access URL 55 | - **Value**: CloudFront distribution domain name 56 | - **Example**: `d1234abcdef.cloudfront.net` 57 | - **Usage**: Access the frontend application using this URL 58 | 59 | ### AdminUser 60 | 61 | - **Description**: The initial admin user for the frontend 62 | - **Value**: Username of the admin user 63 | - **Example**: `admin` 64 | - **Usage**: Use this username with the password sent to the admin email address to log in 65 | 66 | ## Advanced Configuration 67 | 68 | ### Customizing the CloudFormation Template 69 | 70 | The CloudFormation template can be customized to meet specific requirements: 71 | 72 | #### Modifying DynamoDB Capacity 73 | 74 | ```yaml 75 | SurveysConfigDDBTable: 76 | Type: AWS::DynamoDB::Table 77 | Properties: 78 | # ... other properties ... 79 | ProvisionedThroughput: 80 | ReadCapacityUnits: 5 # Modify as needed 81 | WriteCapacityUnits: 5 # Modify as needed 82 | ``` 83 | 84 | #### Customizing Lambda Function Memory 85 | 86 | ```yaml 87 | LambdaSurveyApi: 88 | Type: AWS::Lambda::Function 89 | Properties: 90 | # ... other properties ... 91 | MemorySize: 256 # Modify as needed 92 | ``` 93 | 94 | #### Adding Custom Domain Name 95 | 96 | ```yaml 97 | CloudFrontDistribution: 98 | Type: "AWS::CloudFront::Distribution" 99 | Properties: 100 | DistributionConfig: 101 | # ... other properties ... 102 | Aliases: 103 | - surveys.example.com 104 | ``` 105 | 106 | #### Enabling DynamoDB Auto Scaling 107 | 108 | ```yaml 109 | SurveysConfigTableReadCapacityScalableTarget: 110 | Type: AWS::ApplicationAutoScaling::ScalableTarget 111 | Properties: 112 | MaxCapacity: 100 113 | MinCapacity: 1 114 | ResourceId: !Sub table/${SurveysConfigDDBTable} 115 | RoleARN: !GetAtt ScalingRole.Arn 116 | ScalableDimension: dynamodb:table:ReadCapacityUnits 117 | ServiceNamespace: dynamodb 118 | ``` 119 | 120 | ## Deployment Considerations 121 | 122 | ### Resource Limits 123 | 124 | - **Lambda Functions**: The solution deploys 7 Lambda functions 125 | - **DynamoDB Tables**: The solution creates 2 DynamoDB tables 126 | - **S3 Buckets**: The solution creates 2 S3 buckets 127 | - **CloudFront Distributions**: The solution creates 1 CloudFront distribution 128 | - **Cognito User Pools**: The solution creates 1 Cognito user pool 129 | 130 | ### Regional Availability 131 | 132 | The solution can be deployed in any AWS region that supports the following services: 133 | 134 | - Amazon Connect 135 | - AWS Lambda 136 | - Amazon DynamoDB 137 | - Amazon S3 138 | - Amazon CloudFront 139 | - Amazon Cognito 140 | - Amazon API Gateway 141 | - Amazon Lex 142 | 143 | ### Cost Considerations 144 | 145 | The solution uses the following billable AWS resources: 146 | 147 | - **Lambda Functions**: Charged based on invocation count and execution time 148 | - **DynamoDB Tables**: Charged based on provisioned capacity and storage 149 | - **S3 Buckets**: Charged based on storage and requests 150 | - **CloudFront Distribution**: Charged based on data transfer and requests 151 | - **API Gateway**: Charged based on API calls 152 | - **Cognito User Pool**: Free tier available, then charged per MAU 153 | - **Amazon Lex**: Charged per request 154 | 155 | For detailed pricing information, refer to the AWS Pricing Calculator. 156 | -------------------------------------------------------------------------------- /docs/frontend/README.md: -------------------------------------------------------------------------------- 1 | # Frontend Documentation 2 | 3 | The frontend of the Amazon Connect Post-Contact Survey Solution is built using React.js and provides a user interface for managing surveys and viewing results. 4 | 5 | ## Component Structure 6 | 7 | ``` 8 | src/ 9 | ├── components/ 10 | │ ├── Authentication/ # Authentication components 11 | │ ├── ChangePassword/ # Password management 12 | │ ├── ForgotPassword/ # Password recovery 13 | │ ├── Home/ # Dashboard component 14 | │ ├── Logout/ # Logout handling 15 | │ ├── Survey/ # Survey management 16 | │ └── SurveysList/ # Survey listing 17 | ├── models/ 18 | │ └── SurveyModel.tsx # Survey data model 19 | ├── App.tsx # Main application component 20 | └── index.tsx # Application entry point 21 | ``` 22 | 23 | ## Key Components 24 | 25 | ### Authentication Components 26 | - Login form 27 | - Password management 28 | - Session handling 29 | - Protected route implementation 30 | 31 | ### Survey Management Components 32 | - Survey creation and editing 33 | - Question configuration 34 | - Response threshold settings 35 | - Results viewing 36 | 37 | ### Data Models 38 | 39 | ```typescript 40 | export interface SurveyModel { 41 | surveyId: string; 42 | surveyName: string; 43 | max: number; 44 | min: number; 45 | introPrompt: string; 46 | outroPrompt: string; 47 | questions: string[]; 48 | flags: number[]; 49 | } 50 | ``` 51 | 52 | ## State Management 53 | 54 | The application uses React's built-in state management with hooks: 55 | 56 | ```typescript 57 | // Example state management in a component 58 | const [surveys, setSurveys] = useState([]); 59 | const [loading, setLoading] = useState(false); 60 | const [error, setError] = useState(null); 61 | ``` 62 | 63 | ## API Integration 64 | 65 | ### Survey Creation 66 | ```typescript 67 | const createSurvey = async (survey: SurveyModel) => { 68 | const response = await axios.post('/api/surveys', { 69 | operation: 'create', 70 | survey: survey 71 | }); 72 | return response.data; 73 | }; 74 | ``` 75 | 76 | ### Survey Results Processing 77 | ```typescript 78 | const processSurveyResponse = async (response) => { 79 | await axios.post('/api/surveys/results', { 80 | surveyId: response.surveyId, 81 | contactId: response.contactId, 82 | responses: response.answers 83 | }); 84 | }; 85 | ``` 86 | 87 | ## Authentication Flow 88 | 89 | 1. User enters credentials 90 | 2. Cognito authentication 91 | 3. JWT token storage 92 | 4. Protected route access 93 | 94 | ```typescript 95 | // Example protected route 96 | const ProtectedRoute = ({ children }) => { 97 | const { isAuthenticated } = useAuth(); 98 | return isAuthenticated ? children : ; 99 | }; 100 | ``` 101 | 102 | ## Error Handling 103 | 104 | ```typescript 105 | try { 106 | const result = await createSurvey(surveyData); 107 | // Handle success 108 | } catch (error) { 109 | if (error.response) { 110 | // Handle API error 111 | setError(error.response.data.message); 112 | } else { 113 | // Handle network error 114 | setError('Network error occurred'); 115 | } 116 | } 117 | ``` 118 | 119 | ## Component Examples 120 | 121 | ### Survey Form 122 | ```typescript 123 | const SurveyForm = () => { 124 | const [survey, setSurvey] = useState({ 125 | surveyId: '', 126 | surveyName: '', 127 | max: 5, 128 | min: 1, 129 | introPrompt: '', 130 | outroPrompt: '', 131 | questions: [], 132 | flags: [] 133 | }); 134 | 135 | const handleSubmit = async (e: React.FormEvent) => { 136 | e.preventDefault(); 137 | try { 138 | await createSurvey(survey); 139 | // Handle success 140 | } catch (error) { 141 | // Handle error 142 | } 143 | }; 144 | 145 | return ( 146 |
147 | {/* Form fields */} 148 |
149 | ); 150 | }; 151 | ``` 152 | 153 | ### Survey List 154 | ```typescript 155 | const SurveyList = () => { 156 | const [surveys, setSurveys] = useState([]); 157 | 158 | useEffect(() => { 159 | const fetchSurveys = async () => { 160 | const response = await axios.get('/api/surveys'); 161 | setSurveys(response.data); 162 | }; 163 | fetchSurveys(); 164 | }, []); 165 | 166 | return ( 167 |
168 | {surveys.map(survey => ( 169 | 170 | ))} 171 |
172 | ); 173 | }; 174 | ``` 175 | 176 | ## Styling 177 | 178 | The application uses CSS modules for styling: 179 | 180 | ```css 181 | /* App.css */ 182 | .container { 183 | max-width: 1200px; 184 | margin: 0 auto; 185 | padding: 20px; 186 | } 187 | 188 | .surveyForm { 189 | display: flex; 190 | flex-direction: column; 191 | gap: 20px; 192 | } 193 | ``` 194 | 195 | ## Build and Deployment 196 | 197 | The frontend is built using Create React App and deployed to S3: 198 | 199 | ```bash 200 | # Build the application 201 | npm run build 202 | 203 | # Deploy to S3 204 | aws s3 sync build/ s3://your-bucket-name 205 | ``` 206 | 207 | ## Testing 208 | 209 | ```typescript 210 | // Example test 211 | import { render, screen } from '@testing-library/react'; 212 | import SurveyForm from './SurveyForm'; 213 | 214 | test('renders survey form', () => { 215 | render(); 216 | expect(screen.getByText('Create Survey')).toBeInTheDocument(); 217 | }); 218 | ``` 219 | 220 | ## Performance Considerations 221 | 222 | - Lazy loading of components 223 | - Memoization of expensive computations 224 | - Efficient re-rendering strategies 225 | - API request caching 226 | 227 | ## Accessibility 228 | 229 | - ARIA labels 230 | - Keyboard navigation 231 | - Color contrast 232 | - Screen reader support 233 | 234 | ## Browser Support 235 | 236 | - Modern browsers (Chrome, Firefox, Safari, Edge) 237 | - Polyfills for older browsers 238 | - Responsive design for mobile devices 239 | -------------------------------------------------------------------------------- /docs/frontend/components.md: -------------------------------------------------------------------------------- 1 | # Frontend Components 2 | 3 | This document provides detailed information about the React components used in the Amazon Connect Post-Contact Survey Solution. 4 | 5 | ## Component Hierarchy 6 | 7 | ``` 8 | App 9 | ├── Authentication 10 | │ ├── Login 11 | │ ├── ForgotPassword 12 | │ └── ChangePassword 13 | ├── Home 14 | │ └── Dashboard 15 | ├── SurveysList 16 | │ ├── SurveyItem 17 | │ └── SurveyFilter 18 | ├── Survey 19 | │ ├── SurveyForm 20 | │ ├── QuestionEditor 21 | │ └── FlagThresholdEditor 22 | └── SurveyResults 23 | ├── ResultsTable 24 | ├── ResultsChart 25 | └── ResultsExport 26 | ``` 27 | 28 | ## Authentication Components 29 | 30 | ### Login Component 31 | 32 | The Login component handles user authentication through Amazon Cognito. 33 | 34 | **Props:** 35 | - `onLogin`: Function to call after successful login 36 | 37 | **State:** 38 | - `username`: String containing the username 39 | - `password`: String containing the password 40 | - `error`: Error message if authentication fails 41 | 42 | **Methods:** 43 | - `handleSubmit`: Handles form submission 44 | - `validateForm`: Validates form inputs 45 | 46 | **Example Usage:** 47 | ```jsx 48 | navigate('/dashboard')} /> 49 | ``` 50 | 51 | ### ForgotPassword Component 52 | 53 | Handles password recovery through Amazon Cognito. 54 | 55 | **Props:** 56 | - `onSuccess`: Function to call after successful password reset 57 | 58 | **State:** 59 | - `username`: String containing the username 60 | - `verificationCode`: String containing the verification code 61 | - `newPassword`: String containing the new password 62 | - `step`: Current step in the password reset flow 63 | 64 | **Methods:** 65 | - `requestCode`: Requests a verification code 66 | - `resetPassword`: Resets the password with the verification code 67 | 68 | **Example Usage:** 69 | ```jsx 70 | navigate('/login')} /> 71 | ``` 72 | 73 | ### ChangePassword Component 74 | 75 | Allows authenticated users to change their password. 76 | 77 | **Props:** 78 | - `onSuccess`: Function to call after successful password change 79 | 80 | **State:** 81 | - `currentPassword`: String containing the current password 82 | - `newPassword`: String containing the new password 83 | - `confirmPassword`: String containing the password confirmation 84 | 85 | **Methods:** 86 | - `changePassword`: Changes the user's password 87 | 88 | **Example Usage:** 89 | ```jsx 90 | setShowSuccessMessage(true)} /> 91 | ``` 92 | 93 | ## Survey Management Components 94 | 95 | ### SurveysList Component 96 | 97 | Displays a list of available surveys. 98 | 99 | **Props:** 100 | - `onSelectSurvey`: Function to call when a survey is selected 101 | 102 | **State:** 103 | - `surveys`: Array of survey objects 104 | - `loading`: Boolean indicating if surveys are being loaded 105 | - `error`: Error message if loading fails 106 | 107 | **Methods:** 108 | - `fetchSurveys`: Fetches surveys from the API 109 | - `deleteSurvey`: Deletes a survey 110 | 111 | **Example Usage:** 112 | ```jsx 113 | setSelectedSurvey(survey)} /> 114 | ``` 115 | 116 | ### Survey Component 117 | 118 | Manages the creation and editing of surveys. 119 | 120 | **Props:** 121 | - `survey`: Survey object to edit (optional) 122 | - `onSave`: Function to call after saving the survey 123 | 124 | **State:** 125 | - `surveyData`: Object containing survey data 126 | - `errors`: Object containing validation errors 127 | - `saving`: Boolean indicating if the survey is being saved 128 | 129 | **Methods:** 130 | - `handleSubmit`: Handles form submission 131 | - `addQuestion`: Adds a new question 132 | - `removeQuestion`: Removes a question 133 | - `updateQuestion`: Updates a question 134 | - `updateFlag`: Updates a flag threshold 135 | 136 | **Example Usage:** 137 | ```jsx 138 | fetchSurveys()} /> 139 | ``` 140 | 141 | ### QuestionEditor Component 142 | 143 | Edits individual survey questions. 144 | 145 | **Props:** 146 | - `question`: Question text 147 | - `index`: Question index 148 | - `onChange`: Function to call when the question changes 149 | - `onRemove`: Function to call when the question is removed 150 | 151 | **State:** 152 | - `text`: Question text 153 | 154 | **Methods:** 155 | - `handleChange`: Handles text input changes 156 | 157 | **Example Usage:** 158 | ```jsx 159 | updateQuestion(0, text)} 163 | onRemove={() => removeQuestion(0)} 164 | /> 165 | ``` 166 | 167 | ### FlagThresholdEditor Component 168 | 169 | Edits flag thresholds for survey questions. 170 | 171 | **Props:** 172 | - `threshold`: Current threshold value 173 | - `index`: Question index 174 | - `min`: Minimum possible value 175 | - `max`: Maximum possible value 176 | - `onChange`: Function to call when the threshold changes 177 | 178 | **State:** 179 | - `value`: Threshold value 180 | 181 | **Methods:** 182 | - `handleChange`: Handles value changes 183 | 184 | **Example Usage:** 185 | ```jsx 186 | updateFlag(0, value)} 192 | /> 193 | ``` 194 | 195 | ## Results Components 196 | 197 | ### SurveyResults Component 198 | 199 | Displays survey results. 200 | 201 | **Props:** 202 | - `surveyId`: ID of the survey to display results for 203 | 204 | **State:** 205 | - `results`: Array of result objects 206 | - `loading`: Boolean indicating if results are being loaded 207 | - `error`: Error message if loading fails 208 | 209 | **Methods:** 210 | - `fetchResults`: Fetches results from the API 211 | - `exportResults`: Exports results to CSV 212 | 213 | **Example Usage:** 214 | ```jsx 215 | 216 | ``` 217 | 218 | ### ResultsTable Component 219 | 220 | Displays survey results in a table format. 221 | 222 | **Props:** 223 | - `results`: Array of result objects 224 | - `questions`: Array of question texts 225 | 226 | **Example Usage:** 227 | ```jsx 228 | 229 | ``` 230 | 231 | ### ResultsChart Component 232 | 233 | Displays survey results in a chart format. 234 | 235 | **Props:** 236 | - `results`: Array of result objects 237 | - `questions`: Array of question texts 238 | - `chartType`: Type of chart to display 239 | 240 | **Example Usage:** 241 | ```jsx 242 | 247 | ``` 248 | 249 | ## Utility Components 250 | 251 | ### ErrorBoundary Component 252 | 253 | Catches JavaScript errors in child components. 254 | 255 | **Props:** 256 | - `children`: Child components 257 | - `fallback`: Component to display when an error occurs 258 | 259 | **State:** 260 | - `hasError`: Boolean indicating if an error has occurred 261 | - `error`: Error object 262 | 263 | **Methods:** 264 | - `componentDidCatch`: Catches errors in child components 265 | 266 | **Example Usage:** 267 | ```jsx 268 | }> 269 | 270 | 271 | ``` 272 | 273 | ### LoadingSpinner Component 274 | 275 | Displays a loading spinner. 276 | 277 | **Props:** 278 | - `size`: Size of the spinner 279 | - `message`: Message to display 280 | 281 | **Example Usage:** 282 | ```jsx 283 | 284 | ``` 285 | 286 | ### Pagination Component 287 | 288 | Handles pagination for lists. 289 | 290 | **Props:** 291 | - `currentPage`: Current page number 292 | - `totalPages`: Total number of pages 293 | - `onPageChange`: Function to call when the page changes 294 | 295 | **Example Usage:** 296 | ```jsx 297 | setCurrentPage(page)} 301 | /> 302 | ``` 303 | -------------------------------------------------------------------------------- /docs/infra.dot: -------------------------------------------------------------------------------- 1 | digraph INFRA { 2 | node [ color = "black", fillcolor = "#E6E6E6", height =1, style = "filled,bold,rounded", fontname = "Arial" ]; 3 | "BlogArtifacts" [ label = "BlogArtifacts 4 | (AWS::S3::Bucket)", shape =cylinder, fillcolor = "#FFF5CD" ]; 5 | "CopyArtifactsLambdaIamRole" [ label = "CopyArtifactsLambdaIamRole 6 | (AWS::IAM::Role)", shape =rectangle ]; 7 | "CustomResourceCopySourceFunction" [ label = "CustomResourceCopySourceFunction 8 | (AWS::Lambda::Function)", shape =rectangle, fillcolor = "#B7E0FF" ]; 9 | "CopyCfnStacksLambdaTrigger" [ label = "CopyCfnStacksLambdaTrigger 10 | (Custom::CopyCfnResources)", shape =rectangle ]; 11 | "CopyArtifactsLambdaIamRole" -> "BlogArtifacts"; 12 | "CustomResourceCopySourceFunction" -> "CopyArtifactsLambdaIamRole"; 13 | "CopyCfnStacksLambdaTrigger" -> "CustomResourceCopySourceFunction"; 14 | "CopyCfnStacksLambdaTrigger" -> "BlogArtifacts"; 15 | } 16 | -------------------------------------------------------------------------------- /docs/infra.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | INFRA 11 | 12 | 13 | 14 | BlogArtifacts 15 | 16 | 17 | BlogArtifacts 18 | (AWS::S3::Bucket) 19 | 20 | 21 | 22 | CopyArtifactsLambdaIamRole 23 | 24 | CopyArtifactsLambdaIamRole 25 | (AWS::IAM::Role) 26 | 27 | 28 | 29 | CopyArtifactsLambdaIamRole->BlogArtifacts 30 | 31 | 32 | 33 | 34 | 35 | CustomResourceCopySourceFunction 36 | 37 | CustomResourceCopySourceFunction 38 | (AWS::Lambda::Function) 39 | 40 | 41 | 42 | CustomResourceCopySourceFunction->CopyArtifactsLambdaIamRole 43 | 44 | 45 | 46 | 47 | 48 | CopyCfnStacksLambdaTrigger 49 | 50 | CopyCfnStacksLambdaTrigger 51 | (Custom::CopyCfnResources) 52 | 53 | 54 | 55 | CopyCfnStacksLambdaTrigger->BlogArtifacts 56 | 57 | 58 | 59 | 60 | 61 | CopyCfnStacksLambdaTrigger->CustomResourceCopySourceFunction 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/user-guides/README.md: -------------------------------------------------------------------------------- 1 | # User Guides 2 | 3 | This section provides comprehensive guides for users of the Amazon Connect Post-Contact Survey Solution. 4 | 5 | ## Available Guides 6 | 7 | - [Administrator Guide](./admin-guide.md): For administrators who manage the solution 8 | - [Contact Center Manager Guide](./contact-center-manager-guide.md): For managers who configure surveys 9 | - [Agent Guide](./agent-guide.md): For agents who handle tasks created from survey responses 10 | 11 | ## Quick Start 12 | 13 | ### For Administrators 14 | 15 | 1. Access the frontend application using the URL provided in the CloudFormation outputs 16 | 2. Log in using the admin credentials sent to the specified email address 17 | 3. Create additional users as needed 18 | 4. Configure system settings 19 | 20 | ### For Contact Center Managers 21 | 22 | 1. Log in to the frontend application 23 | 2. Create a new survey with appropriate questions and flag thresholds 24 | 3. Configure contact flows to trigger the survey 25 | 4. Monitor survey results and tasks 26 | 27 | ### For Agents 28 | 29 | 1. Log in to Amazon Connect 30 | 2. Handle tasks created from flagged survey responses 31 | 3. Follow up with customers as needed 32 | -------------------------------------------------------------------------------- /docs/user-guides/admin-guide.md: -------------------------------------------------------------------------------- 1 | # Administrator Guide 2 | 3 | This guide provides detailed instructions for administrators of the Amazon Connect Post-Contact Survey Solution. 4 | 5 | ## Table of Contents 6 | 7 | - [Accessing the Admin Interface](#accessing-the-admin-interface) 8 | - [User Management](#user-management) 9 | - [System Configuration](#system-configuration) 10 | - [Monitoring and Maintenance](#monitoring-and-maintenance) 11 | - [Troubleshooting](#troubleshooting) 12 | 13 | ## Accessing the Admin Interface 14 | 15 | ### Initial Login 16 | 17 | 1. Access the frontend application using the URL provided in the CloudFormation outputs 18 | 2. Log in using the admin credentials sent to the specified email address 19 | - Username: `admin` 20 | - Password: Check your email for the temporary password 21 | 3. You will be prompted to change your password on first login 22 | 23 | ### Password Requirements 24 | 25 | - Minimum length: 8 characters 26 | - Must include at least one: 27 | - Uppercase letter 28 | - Lowercase letter 29 | - Number 30 | - Special character 31 | 32 | ## User Management 33 | 34 | The Amazon Connect Post-Contact Survey Solution uses Amazon Cognito for authentication. User management is performed directly through the AWS Cognito console, not through the frontend application. 35 | 36 | ### Creating Users 37 | 38 | 1. Log in to the AWS Console 39 | 2. Navigate to Amazon Cognito > User Pools 40 | 3. Select the user pool created for the solution (typically named `contact-surveys-user-pool` or similar) 41 | 4. Click "Create user" 42 | 5. Enter the user's details: 43 | - Username 44 | - Email address 45 | - Temporary password 46 | 6. Select "Mark email as verified" if needed 47 | 7. Click "Create user" 48 | 49 | ### Resetting User Passwords 50 | 51 | 1. Navigate to the Cognito user pool 52 | 2. Find the user in the list 53 | 3. Click on the username 54 | 4. Click "Reset password" 55 | 5. Choose whether to send an email with a reset link or set a temporary password directly 56 | 57 | ### Deactivating Users 58 | 59 | 1. Navigate to the Cognito user pool 60 | 2. Find the user in the list 61 | 3. Click on the username 62 | 4. Click "Disable user" (or "Delete user" to remove completely) 63 | 64 | ### Using AWS CLI for User Management 65 | 66 | You can also manage users using the AWS CLI: 67 | 68 | #### Create a User 69 | ```bash 70 | aws cognito-idp admin-create-user \ 71 | --user-pool-id \ 72 | --username \ 73 | --user-attributes Name=email,Value= Name=email_verified,Value=true \ 74 | --temporary-password 75 | ``` 76 | 77 | #### Confirm a User 78 | ```bash 79 | aws cognito-idp admin-confirm-sign-up \ 80 | --user-pool-id \ 81 | --username 82 | ``` 83 | 84 | #### Reset a User's Password 85 | ```bash 86 | aws cognito-idp admin-reset-user-password \ 87 | --user-pool-id \ 88 | --username 89 | ``` 90 | 91 | ## System Configuration 92 | 93 | ### Configuring Amazon Connect Integration 94 | 95 | The integration with Amazon Connect is configured during deployment through the CloudFormation template. To modify the configuration: 96 | 97 | 1. Update the CloudFormation stack parameters: 98 | - `AmazonConnectInstanceARN` 99 | - `AmazonConnectInstanceName` 100 | - `ContactFlowIdForTasks` 101 | 102 | 2. Configure contact flows in Amazon Connect to use the survey module 103 | 104 | ### Configuring Survey Defaults 105 | 106 | Survey defaults are configured within each survey. There are no global defaults in the current implementation. 107 | 108 | ## Monitoring and Maintenance 109 | 110 | ### Monitoring System Health 111 | 112 | Monitor the health of the solution components using AWS CloudWatch: 113 | 114 | 1. Log in to the AWS Console 115 | 2. Navigate to CloudWatch > Dashboards 116 | 3. Create a dashboard with widgets for: 117 | - Lambda function invocations and errors 118 | - API Gateway requests and latency 119 | - DynamoDB read/write capacity 120 | - CloudFront requests and errors 121 | 122 | ### Viewing Logs 123 | 124 | 1. Navigate to CloudWatch > Log Groups 125 | 2. Find the log groups for the solution components: 126 | - `/aws/lambda/contact-surveys-surveys-api` 127 | - `/aws/lambda/contact-surveys-surveys-get-survey-config` 128 | - `/aws/lambda/contact-surveys-surveys-write-results` 129 | - `/aws/lambda/contact-surveys-process-survey-flags` 130 | - `/aws/lambda/contact-surveys-utils` 131 | 132 | ### Backing Up Data 133 | 134 | To back up survey configurations and results: 135 | 136 | #### DynamoDB Backup 137 | ```bash 138 | aws dynamodb create-backup \ 139 | --table-name contact-surveys-surveys-config \ 140 | --backup-name config-backup-$(date +%Y%m%d) 141 | 142 | aws dynamodb create-backup \ 143 | --table-name contact-surveys-surveys-results \ 144 | --backup-name results-backup-$(date +%Y%m%d) 145 | ``` 146 | 147 | #### Export to S3 148 | ```bash 149 | aws dynamodb export-table-to-point-in-time \ 150 | --table-arn \ 151 | --s3-bucket \ 152 | --s3-prefix \ 153 | --export-format DYNAMODB_JSON 154 | ``` 155 | 156 | ### Restoring Data 157 | 158 | To restore from a DynamoDB backup: 159 | 160 | ```bash 161 | aws dynamodb restore-table-from-backup \ 162 | --target-table-name contact-surveys-surveys-config-restored \ 163 | --backup-arn 164 | ``` 165 | 166 | ## Troubleshooting 167 | 168 | ### Common Issues 169 | 170 | #### Authentication Issues 171 | 172 | **Issue**: "User is not confirmed" 173 | 174 | **Solution**: 175 | ```bash 176 | aws cognito-idp admin-confirm-sign-up \ 177 | --user-pool-id \ 178 | --username 179 | ``` 180 | 181 | #### Survey Creation Failures 182 | 183 | **Issue**: Survey creation fails 184 | 185 | **Solution**: 186 | 1. Check Lambda CloudWatch logs: `/aws/lambda/contact-surveys-surveys-api` 187 | 2. Verify DynamoDB permissions 188 | 3. Enable debug logging: 189 | ```javascript 190 | const debugMode = true; 191 | console.debug('Survey creation payload:', surveyData); 192 | ``` 193 | 194 | #### Contact Flow Module Not Working 195 | 196 | **Issue**: Survey not triggered after contact 197 | 198 | **Solution**: 199 | 1. Verify the `surveyId` attribute is set correctly 200 | 2. Check Lambda CloudWatch logs for errors 201 | 3. Verify IAM permissions for Lambda functions 202 | 203 | ### Accessing CloudWatch Logs 204 | 205 | 1. Log in to the AWS Console 206 | 2. Navigate to CloudWatch > Log Groups 207 | 3. Find the log group for the relevant Lambda function 208 | 4. Filter logs by: 209 | - Time range 210 | - Error message 211 | - Request ID 212 | 213 | ### Contacting Support 214 | 215 | For additional support: 216 | 217 | 1. Gather relevant information: 218 | - Error messages 219 | - CloudWatch logs 220 | - Steps to reproduce the issue 221 | 2. Contact AWS Support or the solution maintainer 222 | -------------------------------------------------------------------------------- /docs/user-guides/contact-center-manager-guide.md: -------------------------------------------------------------------------------- 1 | # Contact Center Manager Guide 2 | 3 | This guide provides detailed instructions for contact center managers using the Amazon Connect Post-Contact Survey Solution. 4 | 5 | ## Table of Contents 6 | 7 | - [Accessing the Survey Management Interface](#accessing-the-survey-management-interface) 8 | - [Creating Surveys](#creating-surveys) 9 | - [Managing Surveys](#managing-surveys) 10 | - [Viewing Survey Results](#viewing-survey-results) 11 | - [Configuring Amazon Connect](#configuring-amazon-connect) 12 | - [Managing Tasks](#managing-tasks) 13 | 14 | ## Accessing the Survey Management Interface 15 | 16 | 1. Access the frontend application using the URL provided by your administrator 17 | 2. Log in using your credentials 18 | 3. The main dashboard displays available surveys and recent results 19 | 20 | ## Creating Surveys 21 | 22 | ### Creating a New Survey 23 | 24 | 1. Navigate to "Surveys" > "Create New Survey" 25 | 2. Enter the survey details: 26 | - Survey Name: A descriptive name for the survey 27 | - Min/Max Rating: The rating scale (e.g., 1-5) 28 | - Introduction Prompt: The message played at the start of the survey 29 | - Conclusion Prompt: The message played at the end of the survey 30 | 31 | 3. Add questions: 32 | - Click "Add Question" 33 | - Enter the question text 34 | - Set a flag threshold if desired (ratings at or below this value will create tasks) 35 | - Repeat for additional questions 36 | 37 | 4. Click "Save Survey" 38 | 39 | ### Survey Question Best Practices 40 | 41 | - Keep questions clear and concise 42 | - Use consistent rating scales 43 | - Limit the number of questions (3-5 is optimal) 44 | - Order questions from general to specific 45 | - Consider the customer's time and patience 46 | 47 | ### Example Survey 48 | 49 | ``` 50 | Survey Name: Customer Satisfaction 51 | Min Rating: 1 52 | Max Rating: 5 53 | Introduction Prompt: "Thank you for taking our brief survey. Please rate the following on a scale of 1 to 5, where 1 is very dissatisfied and 5 is very satisfied." 54 | Conclusion Prompt: "Thank you for your feedback. We appreciate your business." 55 | 56 | Questions: 57 | 1. "How satisfied were you with the overall service?" (Flag threshold: 3) 58 | 2. "How satisfied were you with the agent's knowledge?" (Flag threshold: 3) 59 | 3. "How likely are you to recommend our service?" (Flag threshold: 3) 60 | ``` 61 | 62 | ## Managing Surveys 63 | 64 | ### Editing Surveys 65 | 66 | 1. Navigate to "Surveys" > "Manage Surveys" 67 | 2. Find the survey in the list 68 | 3. Click "Edit" 69 | 4. Make the necessary changes 70 | 5. Click "Save Changes" 71 | 72 | ### Duplicating Surveys 73 | 74 | 1. Navigate to "Surveys" > "Manage Surveys" 75 | 2. Find the survey in the list 76 | 3. Click "Duplicate" 77 | 4. Modify the survey as needed 78 | 5. Click "Save as New" 79 | 80 | ### Deleting Surveys 81 | 82 | 1. Navigate to "Surveys" > "Manage Surveys" 83 | 2. Find the survey in the list 84 | 3. Click "Delete" 85 | 4. Confirm the deletion 86 | 87 | ## Viewing Survey Results 88 | 89 | ### Accessing Results 90 | 91 | 1. Navigate to "Results" 92 | 2. Select a survey from the dropdown 93 | 3. Set the date range 94 | 4. Click "View Results" 95 | 96 | ### Results Dashboard 97 | 98 | The results dashboard displays: 99 | 100 | - Overall satisfaction metrics 101 | - Question-by-question breakdown 102 | - Trend analysis over time 103 | - Flagged responses that created tasks 104 | 105 | ### Exporting Results 106 | 107 | 1. Navigate to "Results" 108 | 2. Select a survey and date range 109 | 3. Click "Export" 110 | 4. Choose the export format (CSV or Excel) 111 | 5. Click "Download" 112 | 113 | ## Configuring Amazon Connect 114 | 115 | ### Setting Up Survey Triggers 116 | 117 | 1. Log in to the Amazon Connect admin console 118 | 2. Navigate to "Routing" > "Contact flows" 119 | 3. Create or edit a contact flow 120 | 4. Add a "Set contact attributes" block to set the `surveyId` attribute: 121 | - Destination key: `surveyId` 122 | - Value: The ID of your survey (found in the survey management interface) 123 | 5. Add a "Transfer to flow" block 124 | 6. Select the "Contact Survey" module 125 | 7. Connect the blocks in your flow 126 | 127 | ### Example Contact Flow Configuration 128 | 129 | ``` 130 | Start 131 | | 132 | v 133 | [Set contact attributes] 134 | | (Set surveyId = "550e8400-e29b-41d4-a716-446655440000") 135 | v 136 | [Transfer to flow] 137 | | (Select "Contact Survey" module) 138 | v 139 | End 140 | ``` 141 | 142 | ## Managing Tasks 143 | 144 | ### Task Creation 145 | 146 | Tasks are automatically created when survey responses meet the flag criteria: 147 | 148 | 1. Customer completes a survey 149 | 2. Response is at or below the flag threshold 150 | 3. System creates a task in Amazon Connect 151 | 4. Task is routed based on your contact flow configuration 152 | 153 | ### Handling Tasks 154 | 155 | 1. Log in to the Amazon Connect agent workspace 156 | 2. Accept the task 157 | 3. Review the survey response details 158 | 4. Follow your contact center's procedures for follow-up 159 | 5. Complete the task when resolved 160 | 161 | ### Task Prioritization 162 | 163 | Tasks are created with the following attributes: 164 | 165 | - Name: "Flagged Post Call Survey" 166 | - Description: Contains the question and response that triggered the flag 167 | - References: Link to the original contact trace record 168 | 169 | You can configure your contact flows to prioritize these tasks based on: 170 | 171 | - Survey question 172 | - Response value 173 | - Original contact channel 174 | - Agent group 175 | 176 | ## Best Practices 177 | 178 | ### Survey Design 179 | 180 | - Keep surveys short (3-5 questions) 181 | - Use consistent rating scales 182 | - Ask actionable questions 183 | - Consider the customer journey 184 | 185 | ### Response Handling 186 | 187 | - Follow up on flagged responses promptly 188 | - Look for patterns in responses 189 | - Use feedback to improve agent training 190 | - Close the loop with customers when appropriate 191 | 192 | ### Continuous Improvement 193 | 194 | - Regularly review survey questions 195 | - Adjust flag thresholds based on response patterns 196 | - Compare results across teams and time periods 197 | - Use insights to drive process improvements 198 | -------------------------------------------------------------------------------- /examples/1-Survey Example Disconnect: -------------------------------------------------------------------------------- 1 | {"Version":"2019-10-30","StartAction":"764cb88c-f976-4c9e-a869-928e458fb199","Metadata":{"entryPointPosition":{"x":40,"y":40},"ActionMetadata":{"d58f2b67-d40d-4873-90d6-556fec3ddcdc":{"position":{"x":412,"y":153.60000000000002}},"764cb88c-f976-4c9e-a869-928e458fb199":{"position":{"x":171.20000000000002,"y":93.60000000000001},"parameters":{"FlowModuleId":{"displayName":"Contact Survey"}},"contactFlowModuleName":"Contact Survey"}},"name":"Survey Example Disconnect","description":"","type":"contactFlow","status":"published","hash":{}},"Actions":[{"Parameters":{},"Identifier":"d58f2b67-d40d-4873-90d6-556fec3ddcdc","Type":"DisconnectParticipant","Transitions":{}},{"Parameters":{"FlowModuleId":"1bc263e9-09cf-432c-971c-32da62c784af"},"Identifier":"764cb88c-f976-4c9e-a869-928e458fb199","Type":"InvokeFlowModule","Transitions":{"NextAction":"d58f2b67-d40d-4873-90d6-556fec3ddcdc","Errors":[{"NextAction":"d58f2b67-d40d-4873-90d6-556fec3ddcdc","ErrorType":"NoMatchingError"}]}}]} -------------------------------------------------------------------------------- /examples/2-Simple Survey Example: -------------------------------------------------------------------------------- 1 | {"Version":"2019-10-30","StartAction":"ce42704a-22d9-44a7-bf5e-956b7e13cced","Metadata":{"entryPointPosition":{"x":40,"y":40},"ActionMetadata":{"4d351dad-6dc6-4245-81d8-fe9d85ca3f10":{"position":{"x":452,"y":663.2}},"73d4d2dc-e70d-45d2-a28d-66d66357c7e6":{"position":{"x":1015.2,"y":664.8000000000001}},"19de1736-e79e-4c9d-9084-13c5e3a69828":{"position":{"x":758.4000000000001,"y":605.6}},"d7227083-dd83-434f-84c2-7da6530819f9":{"position":{"x":1020.8000000000001,"y":370.40000000000003}},"ce42704a-22d9-44a7-bf5e-956b7e13cced":{"position":{"x":200,"y":113.60000000000001}},"50c74b2e-09e5-4a84-803e-5e22dcd2091a":{"position":{"x":195.20000000000002,"y":604}},"ba8ce768-b923-4625-8d81-0c1ecb86262e":{"position":{"x":760.8000000000001,"y":369.6},"parameters":{"QueueId":{"displayName":"BasicQueue"}},"queue":{"text":"BasicQueue"}},"9c9176ba-a131-4469-820f-7820043b94f5":{"position":{"x":196,"y":372},"parameters":{"EventHooks":{"CustomerRemaining":{"displayName":"Survey Example Disconnect"}}},"contactFlow":{"text":"Survey Example Disconnect","id":"arn:aws:connect:ap-southeast-2:964328442121:instance/7136b212-66af-4bd9-8913-8dec118f5254/contact-flow/9c95f170-90a3-4dad-bb26-4dea76097865"}},"dc75988f-7784-44dc-a3b3-95d712246833":{"position":{"x":444,"y":370.40000000000003},"dynamicParams":[]}},"name":"Simple Survey Example","description":"","type":"contactFlow","status":"published","hash":{}},"Actions":[{"Parameters":{},"Identifier":"4d351dad-6dc6-4245-81d8-fe9d85ca3f10","Type":"DisconnectParticipant","Transitions":{}},{"Parameters":{},"Identifier":"73d4d2dc-e70d-45d2-a28d-66d66357c7e6","Type":"DisconnectParticipant","Transitions":{}},{"Parameters":{"Text":"You forgot to confirm the working queue. Please review the contact flow."},"Identifier":"19de1736-e79e-4c9d-9084-13c5e3a69828","Type":"MessageParticipant","Transitions":{"NextAction":"73d4d2dc-e70d-45d2-a28d-66d66357c7e6","Errors":[{"NextAction":"73d4d2dc-e70d-45d2-a28d-66d66357c7e6","ErrorType":"NoMatchingError"}]}},{"Parameters":{},"Identifier":"d7227083-dd83-434f-84c2-7da6530819f9","Type":"TransferContactToQueue","Transitions":{"NextAction":"73d4d2dc-e70d-45d2-a28d-66d66357c7e6","Errors":[{"NextAction":"73d4d2dc-e70d-45d2-a28d-66d66357c7e6","ErrorType":"QueueAtCapacity"},{"NextAction":"73d4d2dc-e70d-45d2-a28d-66d66357c7e6","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"Welcome to the simple survey demonstration."},"Identifier":"ce42704a-22d9-44a7-bf5e-956b7e13cced","Type":"MessageParticipant","Transitions":{"NextAction":"9c9176ba-a131-4469-820f-7820043b94f5","Errors":[{"NextAction":"9c9176ba-a131-4469-820f-7820043b94f5","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"You forgot to set the disconnect flow. Please review the contact flow."},"Identifier":"50c74b2e-09e5-4a84-803e-5e22dcd2091a","Type":"MessageParticipant","Transitions":{"NextAction":"4d351dad-6dc6-4245-81d8-fe9d85ca3f10","Errors":[{"NextAction":"4d351dad-6dc6-4245-81d8-fe9d85ca3f10","ErrorType":"NoMatchingError"}]}},{"Parameters":{"QueueId":"arn:aws:connect:ap-southeast-2:964328442121:instance/7136b212-66af-4bd9-8913-8dec118f5254/queue/4bca1082-f46a-4580-8c76-79db54b8d49a"},"Identifier":"ba8ce768-b923-4625-8d81-0c1ecb86262e","Type":"UpdateContactTargetQueue","Transitions":{"NextAction":"d7227083-dd83-434f-84c2-7da6530819f9","Errors":[{"NextAction":"19de1736-e79e-4c9d-9084-13c5e3a69828","ErrorType":"NoMatchingError"}]}},{"Parameters":{"EventHooks":{"CustomerRemaining":"arn:aws:connect:ap-southeast-2:964328442121:instance/7136b212-66af-4bd9-8913-8dec118f5254/contact-flow/9c95f170-90a3-4dad-bb26-4dea76097865"}},"Identifier":"9c9176ba-a131-4469-820f-7820043b94f5","Type":"UpdateContactEventHooks","Transitions":{"NextAction":"dc75988f-7784-44dc-a3b3-95d712246833","Errors":[{"NextAction":"50c74b2e-09e5-4a84-803e-5e22dcd2091a","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Attributes":{"surveyId":" "}},"Identifier":"dc75988f-7784-44dc-a3b3-95d712246833","Type":"UpdateContactAttributes","Transitions":{"NextAction":"ba8ce768-b923-4625-8d81-0c1ecb86262e","Errors":[{"NextAction":"ba8ce768-b923-4625-8d81-0c1ecb86262e","ErrorType":"NoMatchingError"}]}}]} -------------------------------------------------------------------------------- /generate-react-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "usesTypeScript": true, 3 | "usesCssModule": false, 4 | "cssPreprocessor": "css", 5 | "testLibrary": "None", 6 | "component": { 7 | "default": { 8 | "path": "src/components", 9 | "withStyle": true, 10 | "withTest": false, 11 | "withStory": false, 12 | "withLazy": false 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /img/Architecture-ExperienceBuilder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/Architecture-ExperienceBuilder.png -------------------------------------------------------------------------------- /img/Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/Architecture.png -------------------------------------------------------------------------------- /img/application-deploy-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/application-deploy-success.png -------------------------------------------------------------------------------- /img/cf-stack-params.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/cf-stack-params.png -------------------------------------------------------------------------------- /img/cloudformation-launch-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/cloudformation-launch-stack.png -------------------------------------------------------------------------------- /img/simple-survey-example-definition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/simple-survey-example-definition.png -------------------------------------------------------------------------------- /img/simple-survey-example-disconnect-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/simple-survey-example-disconnect-flow.png -------------------------------------------------------------------------------- /img/simple-survey-example-flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/simple-survey-example-flag.png -------------------------------------------------------------------------------- /img/simple-survey-example-id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/simple-survey-example-id.png -------------------------------------------------------------------------------- /img/simple-survey-example-inbound-flow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/simple-survey-example-inbound-flow-1.png -------------------------------------------------------------------------------- /img/simple-survey-example-inbound-flow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/simple-survey-example-inbound-flow-2.png -------------------------------------------------------------------------------- /img/simple-survey-example-questions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/simple-survey-example-questions.png -------------------------------------------------------------------------------- /img/simple-survey-example-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/simple-survey-example-results.png -------------------------------------------------------------------------------- /img/simple-survey-example-task-description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/img/simple-survey-example-task-description.png -------------------------------------------------------------------------------- /lambdas/api/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // Import AWS SDK v3 DynamoDB clients 5 | // DynamoDBClient is the low-level client 6 | const { DynamoDBClient } = require('@aws-sdk/client-dynamodb'); 7 | // DynamoDBDocumentClient provides a higher-level abstraction for working with data 8 | const { 9 | DynamoDBDocumentClient, 10 | ScanCommand, 11 | PutCommand, 12 | DeleteCommand 13 | } = require('@aws-sdk/lib-dynamodb'); 14 | // UUID v4 for generating unique identifiers 15 | const { v4: uuid } = require('uuid'); 16 | 17 | // Initialize DynamoDB clients 18 | const client = new DynamoDBClient({}); 19 | // Create document client with default marshalling options 20 | const docClient = DynamoDBDocumentClient.from(client); 21 | 22 | // Define valid operations for the API 23 | const OPERATIONS = ['create', 'update', 'list', 'delete', 'results']; 24 | 25 | /** 26 | * Scans an entire DynamoDB table and returns all items 27 | * Handles pagination automatically using LastEvaluatedKey 28 | * @param {string} tableName - The name of the DynamoDB table to scan 29 | * @returns {Array} Array of items from the table 30 | */ 31 | const scanTable = async (tableName) => { 32 | const params = { 33 | TableName: tableName, 34 | }; 35 | 36 | const scanResults = []; 37 | let lastEvaluatedKey; 38 | 39 | do { 40 | const command = new ScanCommand(params); 41 | const response = await docClient.send(command); 42 | lastEvaluatedKey = response.LastEvaluatedKey; 43 | scanResults.push(...(response.Items || [])); 44 | params.ExclusiveStartKey = lastEvaluatedKey; 45 | } while (lastEvaluatedKey); 46 | 47 | return scanResults; 48 | }; 49 | 50 | /** 51 | * Lambda handler function - processes API requests for survey operations 52 | * @param {Object} event - AWS Lambda event object 53 | * @returns {Object} Response object with status code and body 54 | */ 55 | exports.handler = async (event) => { 56 | // Initialize response object with CORS headers 57 | const response = { 58 | "headers": { 59 | "Content-Type": "application/json", 60 | "Access-Control-Allow-Origin": "*", 61 | } 62 | }; 63 | 64 | let operation = undefined; 65 | 66 | // Validate incoming request 67 | if (!validateRequest()) { 68 | return response; 69 | } 70 | 71 | let body = {}; 72 | 73 | // Process request based on operation type 74 | switch (operation) { 75 | case 'list': 76 | // Retrieve all surveys from the configuration table 77 | let data = await listSurveys(); 78 | 79 | if (data) { 80 | response.statusCode = 200; 81 | body.success = "true"; 82 | body.data = data; 83 | } else { 84 | response.statusCode = 500; 85 | body.success = false; 86 | body.message = "Something went terribly wrong."; 87 | } 88 | 89 | response.body = JSON.stringify(body); 90 | break; 91 | 92 | case 'create': 93 | // Create or update a survey configuration 94 | let surveyData = JSON.parse(event.body).data; 95 | 96 | if (!surveyData) { 97 | response.statusCode = 400; 98 | body.success = false; 99 | body.message = "Unsupported operation."; 100 | response.body = JSON.stringify(body); 101 | } else { 102 | // Transform questions array into object with numbered keys 103 | let questions = {}; 104 | surveyData.questions.forEach((question, index) => { 105 | questions[`question_${index + 1}`] = question; 106 | }); 107 | 108 | // Transform flags array into object with numbered keys 109 | let flags = {}; 110 | surveyData.flags.forEach((flag, index) => { 111 | flags[`flag_question_${index + 1}`] = flag; 112 | }); 113 | 114 | // Generate new UUID if surveyId not provided 115 | const surveyId = surveyData.surveyId || uuid(); 116 | 117 | // Prepare and execute DynamoDB put operation 118 | const putCommand = new PutCommand({ 119 | TableName: process.env.TABLE_SURVEYS_CONFIG, 120 | Item: { 121 | surveyId, 122 | surveyName: surveyData.surveyName, 123 | min: surveyData.min, 124 | max: surveyData.max, 125 | introPrompt: surveyData.introPrompt, 126 | outroPrompt: surveyData.outroPrompt, 127 | ...questions, 128 | ...flags 129 | } 130 | }); 131 | 132 | await docClient.send(putCommand); 133 | 134 | response.statusCode = 200; 135 | body.success = true; 136 | body.data = surveyId; 137 | response.body = JSON.stringify(body); 138 | } 139 | break; 140 | 141 | case 'delete': 142 | // Delete a survey configuration 143 | const surveyIdToDelete = JSON.parse(event.body).data.surveyId; 144 | 145 | const deleteCommand = new DeleteCommand({ 146 | TableName: process.env.TABLE_SURVEYS_CONFIG, 147 | Key: { 148 | surveyId: surveyIdToDelete 149 | } 150 | }); 151 | 152 | await docClient.send(deleteCommand); 153 | 154 | response.statusCode = 200; 155 | body.success = true; 156 | response.body = JSON.stringify(body); 157 | break; 158 | 159 | case 'results': 160 | // Retrieve survey results for a specific survey 161 | const scanCommand = new ScanCommand({ 162 | TableName: process.env.TABLE_SURVEYS_RESULTS, 163 | FilterExpression: "surveyId = :id", 164 | ExpressionAttributeValues: { 165 | ":id": JSON.parse(event.body).data.surveyId 166 | } 167 | }); 168 | 169 | const results = await getResults(scanCommand); 170 | response.statusCode = 200; 171 | body.success = true; 172 | body.data = results; 173 | response.body = JSON.stringify(body); 174 | break; 175 | 176 | default: 177 | response.statusCode = 400; 178 | body.success = false; 179 | body.message = "Unsupported operation."; 180 | response.body = JSON.stringify(body); 181 | break; 182 | } 183 | 184 | return response; 185 | 186 | /** 187 | * Retrieves all results for a specific survey 188 | * Handles pagination for large result sets 189 | * @param {ScanCommand} scanCommand - Prepared scan command for DynamoDB 190 | * @returns {Array} Array of survey results 191 | */ 192 | async function getResults(scanCommand) { 193 | const scanResults = []; 194 | let lastEvaluatedKey; 195 | 196 | do { 197 | const response = await docClient.send(scanCommand); 198 | lastEvaluatedKey = response.LastEvaluatedKey; 199 | scanResults.push(...(response.Items || [])); 200 | if (lastEvaluatedKey) { 201 | scanCommand.input.ExclusiveStartKey = lastEvaluatedKey; 202 | } 203 | } while (lastEvaluatedKey); 204 | 205 | return scanResults; 206 | } 207 | 208 | /** 209 | * Retrieves all survey configurations 210 | * @returns {Array|undefined} Array of survey configurations or undefined on error 211 | */ 212 | async function listSurveys() { 213 | try { 214 | return await scanTable(process.env.TABLE_SURVEYS_CONFIG); 215 | } catch (e) { 216 | console.log(e); 217 | return undefined; 218 | } 219 | } 220 | 221 | /** 222 | * Validates the incoming request 223 | * Checks for valid JSON body and operation type 224 | * @returns {boolean} True if request is valid, false otherwise 225 | */ 226 | function validateRequest() { 227 | if (event.httpMethod === 'POST') { 228 | try { 229 | var body = JSON.parse(event.body); 230 | } catch (e) { 231 | console.log(e); 232 | response.statusCode = 400; 233 | response.body = "Body is not valid JSON"; 234 | return false; 235 | } 236 | 237 | if (!body.operation) { 238 | response.statusCode = 400; 239 | response.body = "No operation specified"; 240 | return false; 241 | } 242 | 243 | if (!OPERATIONS.includes(body.operation)) { 244 | response.statusCode = 400; 245 | response.body = "Unsupported operation"; 246 | return false; 247 | } 248 | 249 | operation = body.operation; 250 | } 251 | 252 | return true; 253 | } 254 | }; 255 | -------------------------------------------------------------------------------- /lambdas/cognitoValidateUser/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // Import AWS SDK v3 Cognito Identity Provider client 5 | const { 6 | CognitoIdentityProviderClient, 7 | AdminUpdateUserAttributesCommand 8 | } = require("@aws-sdk/client-cognito-identity-provider"); 9 | const https = require("https"); 10 | const url = require("url"); 11 | 12 | // Initialize Cognito Identity Provider client 13 | const client = new CognitoIdentityProviderClient({}); 14 | 15 | /** 16 | * Lambda handler for Custom Resource to validate Cognito user 17 | * This function is typically called by CloudFormation during stack operations 18 | * 19 | * @param {Object} event - CloudFormation Custom Resource event 20 | * @param {Object} context - Lambda context object 21 | * @returns {Object} Response object with status code and empty body 22 | */ 23 | exports.handler = async (event, context) => { 24 | // Handle stack deletion - return success immediately 25 | if (event.RequestType === "Delete") { 26 | await send(event, context, "SUCCESS"); 27 | return; 28 | } 29 | 30 | // Prepare input for updating user attributes 31 | const input = { 32 | UserPoolId: event.ResourceProperties.UserPoolId, 33 | Username: "admin", 34 | UserAttributes: [ 35 | { 36 | Name: "email_verified", 37 | Value: "true", 38 | }, 39 | ], 40 | }; 41 | 42 | try { 43 | // Update admin user attributes to verify email 44 | const command = new AdminUpdateUserAttributesCommand(input); 45 | await client.send(command); 46 | } catch (e) { 47 | console.log(e); 48 | await send(event, context, "FAILED"); 49 | } 50 | 51 | const response = { 52 | statusCode: 200, 53 | body: JSON.stringify({}), 54 | }; 55 | 56 | await send(event, context, "SUCCESS"); 57 | 58 | return response; 59 | }; 60 | 61 | /** 62 | * Sends response back to CloudFormation 63 | * This is required for Custom Resources to signal completion to CloudFormation 64 | * 65 | * @param {Object} event - CloudFormation Custom Resource event 66 | * @param {Object} context - Lambda context object 67 | * @param {string} responseStatus - Status of the operation (SUCCESS/FAILED) 68 | * @param {Object} responseData - Additional data to send back to CloudFormation 69 | * @param {string} physicalResourceId - Physical ID of the custom resource 70 | * @param {boolean} noEcho - Whether to mask the response in CloudFormation logs 71 | * @returns {Promise} Promise that resolves when the response is sent 72 | */ 73 | async function send(event, context, responseStatus, responseData, physicalResourceId, noEcho) { 74 | // Prepare the response body for CloudFormation 75 | var responseBody = JSON.stringify({ 76 | Status: responseStatus, 77 | Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName, 78 | PhysicalResourceId: physicalResourceId || context.logStreamName, 79 | StackId: event.StackId, 80 | RequestId: event.RequestId, 81 | LogicalResourceId: event.LogicalResourceId, 82 | NoEcho: noEcho || false, 83 | Data: responseData, 84 | }); 85 | 86 | console.log("Response body:\n", responseBody); 87 | 88 | // Parse the pre-signed URL provided by CloudFormation 89 | var parsedUrl = url.parse(event.ResponseURL); 90 | 91 | // Prepare the HTTPS request options 92 | var options = { 93 | hostname: parsedUrl.hostname, 94 | port: 443, 95 | path: parsedUrl.path, 96 | method: "PUT", 97 | headers: { 98 | "content-type": "", 99 | "content-length": responseBody.length, 100 | }, 101 | }; 102 | 103 | // Create a promise to handle the HTTPS request 104 | const sendPromise = new Promise((_res, _rej) => { 105 | try { 106 | // Send the HTTPS request to CloudFormation 107 | var request = https.request(options, function (response) { 108 | console.log("Status code: " + response.statusCode); 109 | console.log("Status message: " + response.statusMessage); 110 | context.done(); 111 | _res(); 112 | }); 113 | 114 | // Handle any errors in the HTTPS request 115 | request.on("error", function (error) { 116 | console.log("send(..) failed executing https.request(..): " + error); 117 | context.done(); 118 | _rej(); 119 | }); 120 | 121 | // Send the response body 122 | request.write(responseBody); 123 | request.end(); 124 | } catch (e) { 125 | console.log(e); 126 | } 127 | }); 128 | 129 | return await sendPromise; 130 | } 131 | -------------------------------------------------------------------------------- /lambdas/getSurveyConfig/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // Import AWS SDK v3 DynamoDB clients 5 | const { DynamoDBClient } = require('@aws-sdk/client-dynamodb'); 6 | const { DynamoDBDocumentClient, GetCommand } = require('@aws-sdk/lib-dynamodb'); 7 | 8 | // Initialize the DynamoDB client 9 | const client = new DynamoDBClient({}); 10 | // Create a document client with marshalling enabled 11 | const docClient = DynamoDBDocumentClient.from(client); 12 | 13 | /** 14 | * Lambda handler to retrieve survey configuration from DynamoDB 15 | * Used by Amazon Connect to fetch survey details during contact flows 16 | * 17 | * @param {Object} event - Lambda event containing survey parameters 18 | * @returns {Object} Survey configuration including questions and metadata 19 | */ 20 | exports.handler = async (event) => { 21 | // Initialize response object 22 | const response = {}; 23 | 24 | // Prepare DynamoDB query parameters 25 | const params = { 26 | TableName: process.env.TABLE, 27 | Key: { 28 | 'surveyId': event.Details.Parameters.surveyId 29 | } 30 | }; 31 | 32 | try { 33 | // Fetch survey configuration from DynamoDB 34 | const command = new GetCommand(params); 35 | const result = await docClient.send(command); 36 | 37 | response.statusCode = 200; 38 | 39 | if (result.Item) { 40 | response.message = 'OK'; 41 | 42 | // Count the number of questions in the survey 43 | let questionCount = 0; 44 | 45 | // Process each field in the DynamoDB item 46 | Object.keys(result.Item).forEach(key => { 47 | // Copy the item value to the response 48 | response[key] = result.Item[key]; 49 | 50 | // Count fields that start with 'question' to determine survey size 51 | if (key.startsWith('question')) { 52 | questionCount++; 53 | } 54 | }); 55 | 56 | // Add the total number of questions to the response 57 | response.surveySize = questionCount; 58 | } else { 59 | // If no survey found, return appropriate message 60 | response.message = `Couldn't find configuration for survey with id [${event.Details.Parameters.surveyId}]`; 61 | } 62 | 63 | } catch (err) { 64 | // Log any errors that occur during execution 65 | console.log(err); 66 | } 67 | 68 | return response; 69 | }; 70 | -------------------------------------------------------------------------------- /lambdas/processReviewFlags/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // Import AWS SDK v3 Connect client 5 | const { ConnectClient, StartTaskContactCommand } = require("@aws-sdk/client-connect"); 6 | const { v4: uuid } = require("uuid"); 7 | 8 | // Initialize the Connect client 9 | const connect = new ConnectClient({}); 10 | 11 | /** 12 | * Helper method to split a string from the right 13 | * Used for parsing instance ARN 14 | * @param {string} sep - Separator to split on 15 | * @param {number} maxsplit - Maximum number of splits 16 | * @returns {Array} Array of split string parts 17 | */ 18 | String.prototype.rsplit = function (sep, maxsplit) { 19 | var split = this.split(sep); 20 | return maxsplit ? [split.slice(0, -maxsplit).join(sep)].concat(split.slice(-maxsplit)) : split; 21 | }; 22 | 23 | /** 24 | * Lambda handler to process survey flags and create tasks for flagged responses 25 | * @param {Object} event - Event containing survey response details 26 | * @returns {Object} Response with status code 27 | */ 28 | exports.handler = async (event) => { 29 | // Object to store questions that were flagged 30 | let flagged = {}; 31 | 32 | // Get all survey result keys from contact attributes 33 | let surveyKeys = Object.keys(event.Details.ContactData.Attributes) 34 | .filter((o) => o.startsWith("survey_result_")); 35 | surveyKeys.sort(); 36 | 37 | // Process each survey question to check for flags 38 | surveyKeys.forEach((key, index) => { 39 | console.log(`Processing ${key}`); 40 | 41 | // Check if a flag threshold exists for this question 42 | if ( 43 | event.Details.Parameters[`flag_question_${index + 1}`] && 44 | event.Details.Parameters[`flag_question_${index + 1}`] != "" 45 | ) { 46 | console.log( 47 | `Flag exists for ${key} with threshold ${event.Details.Parameters[`flag_question_${index + 1}`]}` 48 | ); 49 | 50 | // Compare response value against flag threshold 51 | if ( 52 | parseInt(event.Details.ContactData.Attributes[key]) <= 53 | parseInt(event.Details.Parameters[`flag_question_${index + 1}`]) 54 | ) { 55 | flagged[key] = event.Details.Parameters[`flag_question_${index + 1}`]; 56 | } 57 | } 58 | }); 59 | 60 | // If any responses were flagged, create a task 61 | if (Object.keys(flagged).length > 0) { 62 | // Extract instance ID from the ARN 63 | let instanceId = event["Details"]["ContactData"]["InstanceARN"].rsplit("/", 1)[1]; 64 | let description = ""; 65 | 66 | // Build description including all flagged questions 67 | Object.keys(flagged).forEach((key) => { 68 | description += `Question ${key.substr(key.length - 1)}: ${ 69 | event["Details"]["ContactData"]["Attributes"][key] 70 | }\n`; 71 | }); 72 | 73 | // Prepare parameters for creating the task 74 | const params = { 75 | ContactFlowId: process.env.CONTACT_FLOW_ID, 76 | InstanceId: instanceId, 77 | Name: "Flagged Post Call Survey", 78 | Attributes: { 79 | surveyId: event["Details"]["ContactData"]["Attributes"]["surveyId"], 80 | contactId: event["Details"]["ContactData"]["ContactId"], 81 | }, 82 | ClientToken: uuid(), 83 | Description: description, 84 | References: { 85 | CTR: { 86 | Type: "URL", 87 | Value: `https://${process.env.INSTANCE_NAME}.my.connect.aws/contact-trace-records/details/${event["Details"]["ContactData"]["ContactId"]}`, 88 | }, 89 | }, 90 | }; 91 | 92 | try { 93 | // Create task in Connect using SDK v3 94 | const command = new StartTaskContactCommand(params); 95 | await connect.send(command); 96 | } catch (err) { 97 | console.log(err); 98 | } 99 | } 100 | 101 | // Return success response 102 | const response = { 103 | statusCode: 200, 104 | body: JSON.stringify("OK"), 105 | }; 106 | return response; 107 | }; 108 | -------------------------------------------------------------------------------- /lambdas/surveyUtils/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | /** 5 | * Lambda handler for survey utility functions 6 | * Supports operations for managing survey flow and input validation 7 | * 8 | * @param {Object} event - Event from Amazon Connect containing operation and parameters 9 | * @returns {Object} Response object with operation-specific data 10 | */ 11 | exports.handler = async (event) => { 12 | // Initialize response with all parameters from the event 13 | const response = { 14 | ...event.Details.Parameters 15 | }; 16 | 17 | const operation = event.Details.Parameters.operation; 18 | 19 | // If no operation specified, return early with success 20 | if (!operation) { 21 | response.success = true; 22 | response.message = "No operation in input. Nothing to do."; 23 | return response; 24 | } 25 | 26 | // Process different operations 27 | switch (operation) { 28 | case "getNextSurveyQuestion": 29 | // Get next question in the survey sequence 30 | const data = getNextSurveyQuestion(); 31 | response.nextQuestion = data.nextQuestion; 32 | response.newCounter = data.newCounter; 33 | response.currentQuestionIndex = data.currentQuestionIndex; 34 | break; 35 | 36 | case "validateInput": 37 | // Validate user input against min/max bounds 38 | response.validInput = `${validateInput()}`; 39 | response.message = `Your answer is not between ${event.Details.Parameters.min} and ${event.Details.Parameters.max}.`; 40 | response.nextQuestion = event.Details.Parameters[`question_${event.Details.ContactData.Attributes.loopCounter}`]; 41 | break; 42 | 43 | default: 44 | // Handle unsupported operations 45 | response.success = false; 46 | response.message = "Unsupported operation."; 47 | } 48 | 49 | return response; 50 | 51 | /** 52 | * Validates if the user input falls within the specified min/max bounds 53 | * @returns {boolean} True if input is valid, false otherwise 54 | */ 55 | function validateInput() { 56 | let min = event.Details.Parameters.min; 57 | let max = event.Details.Parameters.max; 58 | 59 | return parseInt(min) <= parseInt(event.Details.Parameters.input) && 60 | parseInt(max) >= parseInt(event.Details.Parameters.input); 61 | } 62 | 63 | /** 64 | * Gets the next survey question and updates the question counter 65 | * @returns {Object} Object containing next question details and updated counter 66 | */ 67 | function getNextSurveyQuestion() { 68 | let res = { 69 | currentQuestionIndex: event.Details.ContactData.Attributes.loopCounter, 70 | nextQuestion: event.Details.Parameters[`question_${event.Details.ContactData.Attributes.loopCounter}`], 71 | newCounter: parseInt(event.Details.ContactData.Attributes.loopCounter) + 1 72 | }; 73 | 74 | return res; 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /lambdas/writeSurveyResults/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // Import AWS SDK v3 DynamoDB clients 5 | const { DynamoDBClient } = require('@aws-sdk/client-dynamodb'); 6 | const { DynamoDBDocumentClient, PutCommand } = require('@aws-sdk/lib-dynamodb'); 7 | 8 | // Initialize DynamoDB clients 9 | const client = new DynamoDBClient({}); 10 | // Create document client with default marshalling options 11 | const docClient = DynamoDBDocumentClient.from(client); 12 | 13 | /** 14 | * Lambda handler to store survey results in DynamoDB 15 | * Processes survey responses from Amazon Connect contact flow 16 | * 17 | * @param {Object} event - Event containing survey responses and contact details 18 | * @returns {Object} Response indicating success/failure of the operation 19 | */ 20 | exports.handler = async (event) => { 21 | // Initialize object to store survey results 22 | const surveyResults = {}; 23 | // Get all attributes from the contact data 24 | const data = event.Details.ContactData.Attributes; 25 | 26 | // Extract survey-related attributes from contact data 27 | // Only process attributes that start with "survey_result_" 28 | Object.keys(data).forEach(element => { 29 | if (element.startsWith("survey_result_")) { 30 | surveyResults[element] = data[element]; 31 | } 32 | }); 33 | 34 | // Prepare DynamoDB item for storage 35 | const params = { 36 | TableName: process.env.TABLE, 37 | Item: { 38 | // Store contact ID as primary key 39 | contactId: event.Details.ContactData.ContactId, 40 | // Store survey ID for reference 41 | surveyId: event.Details.ContactData.Attributes.surveyId, 42 | // Spread survey results into the item 43 | ...surveyResults, 44 | // Add Unix timestamp for when the results were stored 45 | timestamp: Math.floor(Date.now() / 1000) 46 | } 47 | }; 48 | 49 | try { 50 | // Write survey results to DynamoDB 51 | const command = new PutCommand(params); 52 | await docClient.send(command); 53 | } catch (err) { 54 | // Log any errors that occur during write operation 55 | console.log(err); 56 | } 57 | 58 | // Return success response 59 | const response = { 60 | statusCode: 200, 61 | body: JSON.stringify('OK'), 62 | }; 63 | return response; 64 | }; 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amazon-connect-surveys-react", 3 | "version": "1.0.2", 4 | "private": true, 5 | "dependencies": { 6 | "@aws-amplify/auth": "^4.6.13", 7 | "@awsui/components-react": "^3.0.613", 8 | "@awsui/global-styles": "^1.0.19", 9 | "@testing-library/jest-dom": "^5.16.5", 10 | "@testing-library/react": "^13.4.0", 11 | "@testing-library/user-event": "^13.5.0", 12 | "@types/jest": "^27.5.2", 13 | "@types/node": "^16.11.64", 14 | "@types/react": "^18.2.0", 15 | "@types/react-dom": "^18.0.6", 16 | "axios": "^1.1.3", 17 | "react": "^18.2.0", 18 | "react-dom": "^18.2.0", 19 | "react-router-dom": "^6.4.2", 20 | "react-scripts": "5.0.1", 21 | "typescript": "^4.8.4", 22 | "web-vitals": "^2.1.4" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "jest", 28 | "test:watch": "jest --watch", 29 | "test:coverage": "jest --coverage", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | }, 50 | "devDependencies": { 51 | "jest": "^27.5.1" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /public/config.js: -------------------------------------------------------------------------------- 1 | // this file will be overwritten when the CFT stack is deployed 2 | 3 | window.app_configuration = { 4 | cognito_pool_id: "", 5 | cognito_client_id: "", 6 | api_endpoint: "", 7 | }; 8 | 9 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-connect-contact-surveys/d50af0afb6216e989aded5431a62fe3e223a13f4/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | Contact Surveys for Amazon Connect 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/.env.production: -------------------------------------------------------------------------------- 1 | # SINCE WE NEED TO DO RUNTIME CONFIGURATION, SEE public/config.js -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .navigation-container { 2 | margin-top: -16px; 3 | margin-left: -40px; 4 | margin-right: -40px; 5 | margin-bottom: 20px; 6 | } 7 | 8 | .flex-align-right { 9 | display: flex; 10 | flex-direction: row; 11 | justify-content: right; 12 | } -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import "./App.css"; 3 | import { AppLayout, TopNavigation } from "@awsui/components-react"; 4 | import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom"; 5 | import Home from "./components/Home/Home"; 6 | import logo from "./ico_connect.svg"; 7 | import Authentication from "./components/Authentication/Authentication"; 8 | import ForgotPassword from "./components/ForgotPassword/ForgotPassword"; 9 | import { Auth } from "@aws-amplify/auth"; 10 | import Logout from "./components/Logout/Logout"; 11 | 12 | function App() { 13 | const [isAuthenticated, setIsAuthenticated] = useState(false); 14 | const [utilities, setUtilities] = useState([]); 15 | 16 | var appConfiguration: any = (window as any).app_configuration; 17 | 18 | useEffect(() => { 19 | if (isAuthenticated) { 20 | setUtilities([ 21 | { type: "button", text: "Logout", href: "/logout", external: false }, 22 | ]); 23 | } else { 24 | setUtilities([]); 25 | } 26 | }, [isAuthenticated]); 27 | 28 | Auth.configure({ 29 | Auth: { 30 | userPoolId: appConfiguration.cognito_pool_id, 31 | userPoolWebClientId: appConfiguration.cognito_client_id, 32 | }, 33 | }); 34 | 35 | const authenticated = (value: boolean) => { 36 | setIsAuthenticated(value); 37 | }; 38 | 39 | return ( 40 |
41 | 46 |
47 | 65 |
66 | 67 | 68 | }> 69 | }> 70 | }> 71 | }> 72 | }> 73 | 74 | 75 |
76 | } 77 | /> 78 | 79 | ); 80 | } 81 | 82 | export default App; 83 | -------------------------------------------------------------------------------- /src/components/Authentication/Authentication.css: -------------------------------------------------------------------------------- 1 | .authentication-container { 2 | max-width: 500px; 3 | margin: 0 auto; 4 | margin-top: 48px; 5 | } 6 | 7 | .actions { 8 | display: flex; 9 | flex-direction: row; 10 | gap: 15px; 11 | justify-content: end; 12 | } -------------------------------------------------------------------------------- /src/components/Authentication/Authentication.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, Button, ContentLayout, Header, SpaceBetween, FormField, Input } from "@awsui/components-react"; 2 | import React, { FC, useState } from "react"; 3 | import { Auth } from "@aws-amplify/auth"; 4 | 5 | import "./Authentication.css"; 6 | import { useNavigate } from "react-router-dom"; 7 | import { BaseKeyDetail } from "@awsui/components-react/internal/events"; 8 | 9 | interface AuthenticationProps {} 10 | 11 | const Authentication: FC = (props) => { 12 | const [ user, setUser ] = useState(); 13 | const [isAlert, setIsAlert] = useState(false); 14 | const [alertMessage, setAlertMessage] = useState(""); 15 | const [username, setUsername] = useState(""); 16 | const [password, setPassword] = useState(""); 17 | const [submitted, setSubmitted] = useState(false); 18 | const [needPasswordChange, setNeedPasswordChange] = useState(false); 19 | 20 | const nav = useNavigate(); 21 | 22 | const login = async () => { 23 | setSubmitted(true); 24 | 25 | Auth.signIn(username, password) 26 | .then((data) => { 27 | setUser(data); 28 | if (data.challengeName == "NEW_PASSWORD_REQUIRED") { 29 | setNeedPasswordChange(true); 30 | setSubmitted(false); 31 | setIsAlert(true); 32 | setAlertMessage("Please change your password."); 33 | setPassword(""); 34 | } else { 35 | nav("/"); 36 | } 37 | }) 38 | .catch((err) => { 39 | setIsAlert(true); 40 | setAlertMessage(err.message); 41 | setSubmitted(false); 42 | }); 43 | }; 44 | 45 | const resetPassword = async () => { 46 | nav("/forgot-password"); 47 | }; 48 | 49 | const changePassword = () => { 50 | setSubmitted(true); 51 | Auth.completeNewPassword(user, password).then(data => { 52 | nav("/"); 53 | }) 54 | .catch(err => { 55 | console.log(err); 56 | setSubmitted(false); 57 | }); 58 | } 59 | 60 | const captureEnterKey = (event: CustomEvent) => { 61 | if (event.detail.keyCode === 13) { 62 | event.stopPropagation(); 63 | event.preventDefault(); 64 | 65 | if (username !== "" && password !== "") { 66 | login(); 67 | } 68 | } 69 | } 70 | 71 | return ( 72 |
73 | 76 |
77 | Login 78 |
79 | 80 | {isAlert && {alertMessage}} 81 | 82 | } 83 | > 84 | {!needPasswordChange && ( 85 |
e.preventDefault()}> 86 | 87 | 88 | setUsername(event.detail.value)}> 89 | 90 | 91 | { captureEnterKey(event) })} 94 | disabled={submitted} 95 | value={password} 96 | onChange={(event) => setPassword(event.detail.value)} 97 | > 98 | 99 |
100 | 103 | 106 |
107 |
108 |
109 | )} 110 | {needPasswordChange && ( 111 |
e.preventDefault()}> 112 | 113 | 114 | setPassword(event.detail.value)}> 115 | 116 |
117 | 120 |
121 |
122 |
123 | )} 124 |
125 |
126 | ); 127 | }; 128 | 129 | export default Authentication; 130 | -------------------------------------------------------------------------------- /src/components/ChangePassword/ChangePassword.css: -------------------------------------------------------------------------------- 1 | .ChangePassword {} -------------------------------------------------------------------------------- /src/components/ChangePassword/ChangePassword.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import './ChangePassword.css'; 3 | 4 | interface ChangePasswordProps {} 5 | 6 | const ChangePassword: FC = () => ( 7 |
8 | ChangePassword Component 9 |
10 | ); 11 | 12 | export default ChangePassword; 13 | -------------------------------------------------------------------------------- /src/components/ForgotPassword/ForgotPassword.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, AlertProps, Button, ContentLayout, FormField, Header, Input, SpaceBetween } from '@awsui/components-react'; 2 | import { Auth } from "@aws-amplify/auth"; 3 | import React, { FC, useState } from 'react'; 4 | import { useNavigate } from 'react-router-dom'; 5 | 6 | interface ForgotPasswordProps {} 7 | 8 | const ForgotPassword: FC = () => { 9 | const [isAlert, setIsAlert] = useState(false); 10 | const [alertMessage, setAlertMessage] = useState(""); 11 | const [alertType, setAlertType] = useState("info"); 12 | const [username, setUsername] = useState(""); 13 | const [password, setPassword] = useState(""); 14 | const [submitted, setSubmitted] = useState(false); 15 | const [codeSent, setCodeSent] = useState(false); 16 | const [code, setCode] = useState(""); 17 | 18 | const nav = useNavigate(); 19 | 20 | const submit = async () => { 21 | setSubmitted(true); 22 | setIsAlert(false); 23 | Auth.forgotPassword(username).then(data => { 24 | setIsAlert(true); 25 | alert("info",`A verification code has been sent to your email address ${data.CodeDeliveryDetails.Destination}.`); 26 | setSubmitted(false); 27 | setCodeSent(true); 28 | }) 29 | .catch(err => { 30 | setIsAlert(true); 31 | alert("error", "We could not process your request at this time."); 32 | setSubmitted(false); 33 | }); 34 | } 35 | 36 | const resetPassword = async () => { 37 | setSubmitted(true); 38 | Auth.forgotPasswordSubmit(username, code, password).then(data => { 39 | console.log(data); 40 | setIsAlert(true); 41 | alert("success", "Your password has been successfully reset. Redirecting..."); 42 | 43 | setTimeout(() => { 44 | nav("/login"); 45 | }, 3000); 46 | }) 47 | .catch(err => { 48 | setSubmitted(false); 49 | 50 | let errMessage = err.name === "InvalidPasswordException" ? "Password must combine uppercase, lowercase and special characters, and be at least 8 characters long." : "Unexpected error - try again later." 51 | 52 | setIsAlert(true); 53 | alert("error", errMessage); 54 | }) 55 | } 56 | 57 | const alert = (type: AlertProps.Type, message: string) => { 58 | setAlertType(type); 59 | setAlertMessage(message); 60 | } 61 | 62 | return ( 63 |
64 | 67 |
68 | Forgot password 69 |
70 | 71 | {isAlert && {alertMessage}} 72 | 73 | } 74 | > 75 |
e.preventDefault()}> 76 | 77 | 78 | setUsername(event.detail.value)}> 79 | 80 | { codeSent && ( 81 | 82 | setCode(event.detail.value)}> 83 | 84 | ) } 85 | { codeSent && ( 86 | 87 | setPassword(event.detail.value)}> 88 | 89 | ) } 90 |
91 | 94 |
95 |
96 |
97 |
98 |
99 | ); 100 | } 101 | 102 | export default ForgotPassword; 103 | -------------------------------------------------------------------------------- /src/components/Home/Home.tsx: -------------------------------------------------------------------------------- 1 | import { Button, SpaceBetween } from "@awsui/components-react"; 2 | import { Auth } from "@aws-amplify/auth"; 3 | import React, { FC, useEffect, useState } from "react"; 4 | import { useNavigate } from "react-router-dom"; 5 | import { SurveyModel } from "../../models/SurveyModel"; 6 | import Survey from "../Survey/Survey"; 7 | import SurveysList from "../SurveysList/SurveysList"; 8 | 9 | interface HomeProps { 10 | authenticated: (value: boolean) => void 11 | } 12 | 13 | const Home: FC = (props) => { 14 | const [selectedSurvey, setSelectedSurvey] = useState(); 15 | const [isEditMode, setIsEditMode] = useState(false); 16 | const [isNewSurvey, setIsNewSurvey] = useState(false); 17 | 18 | const nav = useNavigate(); 19 | 20 | useEffect(() => { 21 | Auth.currentAuthenticatedUser() 22 | .then((data) => { 23 | sessionStorage.setItem("jwt", data.signInUserSession.idToken.jwtToken); 24 | props.authenticated(true); 25 | }) 26 | .catch((err) => { 27 | nav("/login"); 28 | }); 29 | }, []); 30 | 31 | useEffect(() => { 32 | if(selectedSurvey?.surveyId !== "") { 33 | setIsEditMode(false); 34 | setIsNewSurvey(false); 35 | } else { 36 | setIsEditMode(true); 37 | setIsNewSurvey(true); 38 | } 39 | }, [selectedSurvey]); 40 | 41 | useEffect(() => { 42 | if (!isEditMode && selectedSurvey?.surveyId === "") { 43 | setSelectedSurvey(undefined); 44 | } 45 | }, [isEditMode]) 46 | 47 | const createSurvey = () => { 48 | let newSurvey: SurveyModel = { 49 | surveyId: "", 50 | surveyName: "", 51 | min: 0, 52 | max: 9, 53 | introPrompt: "", 54 | outroPrompt: "", 55 | questions: [], 56 | flags: [] 57 | } 58 | 59 | setSelectedSurvey(newSurvey); 60 | }; 61 | 62 | return ( 63 | <> 64 | 65 | 66 | {/* */} 69 | 70 | 71 | 72 | {selectedSurvey && setIsEditMode(value)}>} 73 | 74 | 75 | ); 76 | }; 77 | 78 | export default Home; 79 | -------------------------------------------------------------------------------- /src/components/Logout/Logout.css: -------------------------------------------------------------------------------- 1 | .Logout {} -------------------------------------------------------------------------------- /src/components/Logout/Logout.tsx: -------------------------------------------------------------------------------- 1 | import {Auth} from '@aws-amplify/auth'; 2 | import React, { FC, useEffect } from 'react'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import './Logout.css'; 5 | 6 | interface LogoutProps {} 7 | 8 | const Logout: FC = () => { 9 | const nav = useNavigate(); 10 | 11 | useEffect(() => { 12 | Auth.signOut().then(data => { 13 | nav("/"); 14 | }) 15 | .catch(err => { 16 | console.log(err); 17 | }); 18 | }, []); 19 | 20 | return (<>); 21 | } 22 | 23 | export default Logout; 24 | -------------------------------------------------------------------------------- /src/components/SurveysList/SurveysList.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Header, Pagination, Table } from "@awsui/components-react"; 2 | import axios from "axios"; 3 | import React, { FC, useEffect, useState } from "react"; 4 | import { SurveyModel } from "../../models/SurveyModel"; 5 | import { Auth } from "@aws-amplify/auth"; 6 | 7 | interface SurveysListProps { 8 | setSelectedSurvey: (survey: SurveyModel | undefined) => void; 9 | selectedSurveyId: string | undefined; 10 | isNewSurvey: boolean; 11 | } 12 | 13 | const SurveysList: FC = (props) => { 14 | const [selectedItems, setSelectedItems] = useState([]); 15 | const [surveys, setSurveys] = useState([]); 16 | const [displayedSurveys, setDisplayedSurveys] = useState([]); 17 | const [currentPageIndex, setCurrentPageIndex] = useState(1); 18 | const [loading, setLoading] = useState(false); 19 | 20 | var appConfiguration: any = (window as any).app_configuration; 21 | 22 | useEffect(() => { 23 | setLoading(true); 24 | fetchSurveys().then((data) => { 25 | setSurveys(data); 26 | setLoading(false); 27 | }); 28 | }, []); 29 | 30 | useEffect(() => { 31 | setDisplayedSurveys(surveys.slice(0, 5)); 32 | }, [surveys]); 33 | 34 | useEffect(() => { 35 | if (selectedItems.length === 1) { 36 | props.setSelectedSurvey(selectedItems[0]); 37 | } 38 | }, [selectedItems]); 39 | 40 | useEffect(() => { 41 | if (props.selectedSurveyId !== selectedItems[0]?.surveyId) { 42 | if (props.selectedSurveyId === "") { 43 | setSelectedItems([]); 44 | } else { 45 | setLoading(true); 46 | fetchSurveys().then((data) => { 47 | setSurveys(data); 48 | let selectedItem: SurveyModel[] = []; 49 | 50 | if (data.find(o => o.surveyId === props.selectedSurveyId)) { 51 | let selectSurvey: SurveyModel = data.find(o => o.surveyId === props.selectedSurveyId)!; 52 | selectedItem.push(selectSurvey); 53 | } 54 | 55 | setSelectedItems(selectedItem); 56 | setLoading(false); 57 | }); 58 | } 59 | } 60 | }, [props.selectedSurveyId]); 61 | 62 | const paginate = (index: number) => { 63 | setSelectedItems([]); 64 | props.setSelectedSurvey(undefined); 65 | setDisplayedSurveys(surveys.slice((index - 1) * 5, (index - 1) * 5 + 5)); 66 | setCurrentPageIndex(index); 67 | }; 68 | 69 | const refresh = () => { 70 | setSelectedItems([]); 71 | props.setSelectedSurvey(undefined); 72 | setLoading(true); 73 | fetchSurveys().then((data) => { 74 | setSurveys(data); 75 | setLoading(false); 76 | }); 77 | } 78 | 79 | const fetchSurveys = async () => { 80 | const jwt = (await Auth.currentSession()).getIdToken().getJwtToken(); 81 | 82 | return axios.post(appConfiguration.api_endpoint, { operation: "list" }, { headers: { "Authorization": jwt }}).then((res) => { 83 | const surveysList: SurveyModel[] = res.data.data.map((o: any) => { 84 | let questions: string[] = []; 85 | let questionsKeys = Object.keys(o).filter((k) => k.startsWith("question_")); 86 | questionsKeys.sort(); 87 | questionsKeys.forEach((key) => { 88 | questions.push(o[key]); 89 | }); 90 | 91 | let flags: string[] = []; 92 | let flagsKeys = Object.keys(o).filter((k) => k.startsWith("flag_")); 93 | flagsKeys.sort(); 94 | flagsKeys.forEach((key) => { 95 | flags.push(o[key]); 96 | }); 97 | 98 | return { 99 | surveyId: o.surveyId, 100 | surveyName: o.surveyName, 101 | min: parseInt(o.min), 102 | max: parseInt(o.max), 103 | introPrompt: o.introPrompt, 104 | outroPrompt: o.outroPrompt, 105 | questions: questions, 106 | flags: flags, 107 | }; 108 | }); 109 | 110 | return surveysList; 111 | }); 112 | }; 113 | 114 | return ( 115 | setSelectedItems(detail.selectedItems)} 117 | selectedItems={selectedItems} 118 | loading={loading} 119 | columnDefinitions={[ 120 | { 121 | id: "name", 122 | header: "Survey name", 123 | cell: (e) => e.surveyName, 124 | }, 125 | { 126 | id: "id", 127 | header: "Id", 128 | cell: (e) => e.surveyId, 129 | }, 130 | { 131 | id: "questions", 132 | header: "# questions", 133 | cell: (e) => e.questions.length, 134 | }, 135 | ]} 136 | items={displayedSurveys} 137 | loadingText="Loading surveys" 138 | selectionType="single" 139 | trackBy="surveyId" 140 | visibleColumns={["name", "id"]} 141 | empty={ 142 | 143 | No surveys 144 | 145 | No surveys to display. 146 | 147 | 148 | } 149 | header={ 150 |
151 | Published surveys{" "} 152 | 155 |
156 | } 157 | pagination={ 158 | { 162 | paginate(event.detail.currentPageIndex); 163 | }} 164 | ariaLabels={{ 165 | nextPageLabel: "Next page", 166 | previousPageLabel: "Previous page", 167 | pageLabel: (pageNumber) => `Page ${pageNumber} of all pages`, 168 | }} 169 | /> 170 | } 171 | /> 172 | ); 173 | }; 174 | 175 | export default SurveysList; 176 | -------------------------------------------------------------------------------- /src/ico_connect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot( 8 | document.getElementById('root') as HTMLElement 9 | ); 10 | root.render( 11 | 12 | 13 | 14 | ); 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /src/models/SurveyModel.tsx: -------------------------------------------------------------------------------- 1 | export interface SurveyModel { 2 | surveyId: string; 3 | surveyName: string; 4 | max: number; 5 | min: number; 6 | introPrompt: string; 7 | outroPrompt: string; 8 | questions: string[]; 9 | flags: number[]; 10 | } -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------