AWSTemplateFormatVersion: 2010-09-09 Description: "Main stack for the advanced analytics solution, this stack has the option to launch three nested stacks" Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "Backend Stack" Parameters: - storageDestinationS3 - Label: default: "Frontend Stack" Parameters: - EnableFrontendStack - CreateStaticWebpage - ConnectionDataBucket - CcpUrl - SamlUrl - Label: default: "Chat Usage Stack" Parameters: - EnableChatUsageStack - ConnectChatTranscriptBucket - TranscriptBucketPrefix ParameterLabels: storageDestinationS3: default: Backend storage S3 Bucket EnableFrontendStack: default: Deploy frontend stack CreateStaticWebpage: default: Deploy a custom call control panel ConnectionDataBucket: default: S3 Bucket to store call connections data Parameters: # Frontend Parameters EnableFrontendStack: AllowedValues: - 'true' - 'false' Default: 'true' Description: "If this parameter is set as true it will deploy the frontend stack." Type: String CreateStaticWebpage: AllowedValues: - 'true' - 'false' Default: 'true' Description: "Select false if you have an existing custom CCP." Type: String ConnectionDataBucket: Description: The name of the Amazon S3 bucket to store the call connection data captured by the frontend and delivered by firehose. This can be the same bucket as the Backend storage S3 Bucket. Type: String CcpUrl: Description: The HTTPS URL of your Amazon Connect CCP, in the form of https:{Instance Name}.awsapps.com/connect/ccp-v2 # AllowedPattern: '\bhttps:\/\/(?:.)+?\.awsapps\.com\/connect\/ccp\-v2\b' Type: String SamlUrl: Description: The SAML login URL for your instance. Please leave empty if you aren't using SAML. Type: String Default: '' # Chat Usage Parameters EnableChatUsageStack: AllowedValues: - 'true' - 'false' Default: 'true' Description: "If true deploy chat usage stack" Type: String ConnectChatTranscriptBucket: Type: String Description: The S3 bucket where your chat transcripts are stored, for example connect-aaaxxxxxxxx TranscriptBucketPrefix: Type: String Description: The prefix inside the S3 bucket where your transcript are stored, for example connect/instanceName/ChatTranscripts # Backend stack parameters storageDestinationS3: Type: String AllowedPattern: '(?!^(\d+\.)+\d+$)^(^([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])([a-z0-9\-]+((\/))?)*([a-z0-9])+$' ConstraintDescription: "Invalid storage destination" Description: Required. The name of your Amazon S3 bucket to store contact trace records. You can create a new bucket or use an existing bucket. Conditions: EnableFrontendStackCondition: !Equals - !Ref EnableFrontendStack - 'true' CreateStaticPageCondition: !Equals - !Ref CreateStaticWebpage - 'true' EnableChatUsageStackCondition: !Equals - !Ref EnableChatUsageStack - 'true' Resources: SolutionSourceBucket: Type: AWS::S3::Bucket DeletionPolicy: Retain Properties: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled CopyArtifactsLambdaIamRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / Policies: - PolicyName: root PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 's3:PutObject' Resource: !Sub 'arn:aws:s3:::${SolutionSourceBucket}/*' - Effect: Allow Action: - 's3:GetObject' - 's3:GetObjectVersion' Resource: !Sub 'arn:aws:s3:::aws-contact-center-blog/*' - Effect: Allow Action: - 's3:ListBucket' - 's3:ListBucketVersions' Resource: - !Sub 'arn:aws:s3:::aws-contact-center-blog/*' - !Sub 'arn:aws:s3:::${SolutionSourceBucket}/*' ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole CustomResourceCopySourceFunction: Type: 'AWS::Lambda::Function' Properties: Role: !GetAtt CopyArtifactsLambdaIamRole.Arn Handler: "index.handler" Runtime: "nodejs12.x" Code: ZipFile: | const s3 = new (require('aws-sdk')).S3(); const response = require('cfn-response'); const sourceBucket = 'aws-contact-center-blog'; const sourcePrefix = 'usage-analytics'; const sourceObjectArray = ['backend.yaml','frontend.yaml','chatusage.yaml']; exports.handler = async (event, context) => { var result = {responseStatus: 'FAILED', responseData: {Data: 'Never updated'}}; try { console.log(`Received event with type ${event.RequestType}`); if(event.RequestType === 'Create' || event.RequestType === 'Update') { copyResult = await Promise.all( sourceObjectArray.map( async (object) => { s3Result = await s3.copyObject({ Bucket: event.ResourceProperties.SolutionSourceBucket, Key: object, CopySource: `${sourceBucket}/${sourcePrefix}/${object}` }).promise(); console.log(`Finished uploading File with result ${JSON.stringify(s3Result, 0, 4)}`); }), ); result.responseStatus = 'SUCCESS'; result.responseData['Data'] = 'Successfully uploaded files'; } else if (event.RequestType === 'Delete') { result.responseStatus = 'SUCCESS', result.responseData['Data'] = 'Successfully deleted files'; } } catch (error) { console.log(JSON.stringify(error, 0, 4)); result.responseStatus = 'FAILED'; result.responseData['Data'] = 'Failed to process event'; } finally { return await responsePromise(event, context, result.responseStatus, result.responseData, `mainstack`); } }; function responsePromise(event, context, responseStatus, responseData, physicalResourceId) { return new Promise(() => response.send(event, context, responseStatus, responseData, physicalResourceId)); } Timeout: 50 CopyCfnStacksLambdaTrigger: Type: 'Custom::CopyCfnStacksLambdaTrigger' DependsOn: - CustomResourceCopySourceFunction Properties: ServiceToken: !GetAtt CustomResourceCopySourceFunction.Arn RequestToken: ${ClientRequestToken} SolutionSourceBucket: !Ref SolutionSourceBucket FrontendStack: Condition: EnableFrontendStackCondition Type: AWS::CloudFormation::Stack DependsOn: - CopyCfnStacksLambdaTrigger - BackendStack Properties: TemplateURL: !Sub 'https://${SolutionSourceBucket}.s3.amazonaws.com/frontend.yaml' Parameters: ConnectionDataBucket: !Ref ConnectionDataBucket CcpUrl: !Ref CcpUrl SamlUrl: !Ref SamlUrl CreateStaticWebpage: !Ref CreateStaticWebpage ConnectGlueDatabase: Fn::GetAtt: - BackendStack - Outputs.glueCatelog ChatUsageStack: Condition: EnableChatUsageStackCondition Type: AWS::CloudFormation::Stack DependsOn: - CopyCfnStacksLambdaTrigger - BackendStack Properties: TemplateURL: !Sub 'https://${SolutionSourceBucket}.s3.amazonaws.com/chatusage.yaml' Parameters: ConnectChatTranscriptBucket: !Ref ConnectChatTranscriptBucket TranscriptBucketPrefix: !Ref TranscriptBucketPrefix ConnectGlueDatabase: Fn::GetAtt: - BackendStack - Outputs.glueCatelog BackendStack: Type: AWS::CloudFormation::Stack DependsOn: - CopyCfnStacksLambdaTrigger Properties: TemplateURL: !Sub 'https://${SolutionSourceBucket}.s3.amazonaws.com/backend.yaml' Parameters: storageDestinationS3: !Ref storageDestinationS3 Outputs: NeedManualConfigNotification: Description: If this value is True, please manually configure the notification based on the guide here... Condition: EnableChatUsageStackCondition Value: Fn::GetAtt: - ChatUsageStack - Outputs.NeedManualConfigNotification CloudfrontUrl: Description: This is the Url for the custom CCP, please configure this Url in your Amazon Connect application integration. Condition: CreateStaticPageCondition Value: Fn::GetAtt: - FrontendStack - Outputs.CloudfrontDistributionURL CTRKinesisFirehose: Description: The ARN of the Kinesis Firehose that will receive CTR events. Manual configuration required in the Amazon Connect admin -> Data Streaming section.Topic. Value: Fn::GetAtt: - BackendStack - Outputs.CTRKinesisFirehose CtrOnlyUsageReportNamedQuery: Description: Creates Athena query for analyzing call usage based on only the CTR data. Value: Fn::GetAtt: - BackendStack - Outputs.CtrOnlyUsageReportNamedQuery ContactTraceRecordsAthenaNamedQuery: Description: Creates Athena table for CTRs for analysis. Value: Fn::GetAtt: - BackendStack - Outputs.ContactTraceRecordsAthenaNamedQuery CallConnectionsDataNamedQuery: Description: Creates Athena table for call connections for analysis. Condition: EnableFrontendStackCondition Value: Fn::GetAtt: - FrontendStack - Outputs.CallConnectionsDataNamedQuery ChatDetailsAthenaNamedQuery: Description: Creates Athena table for chat transcript details. Condition: EnableChatUsageStackCondition Value: Fn::GetAtt: - ChatUsageStack - Outputs.ChatDetailsAthenaNamedQuery ChatUsageReportAthenaNamedQuery: Description: Creates Athena query for chat usage analytics report Condition: EnableChatUsageStackCondition Value: Fn::GetAtt: - ChatUsageStack - Outputs.ChatUsageReportAthenaNamedQuery UsageReportAthenaNamedQuery: Condition: EnableFrontendStackCondition Description: Creates Athena query for analyzing call times and phone type for usage analytics. Value: Fn::GetAtt: - FrontendStack - Outputs.UsageReportAthenaNamedQuery