AWSTemplateFormatVersion: "2010-09-09"
Transform: 'AWS::Serverless-2016-10-31'


Parameters:
  Environment:
    Type: String
    Default: dev
    Description: Environment name
  LogLevel:
    Type: String
    Default: INFO
  RetentionInDays:
    Type: Number
    Default: 30
    Description: CloudWatch Logs retention period for Lambda functions
  EventBusArn:
    Type: AWS::SSM::Parameter::Value<String>
    Description: EventBridge Event Bus ARN
  EventBusName:
    Type: AWS::SSM::Parameter::Value<String>
    Description: EventBridge Event Bus Name


Globals:
  Function:
    Runtime: python3.9
    Architectures:
      - arm64
    Handler: main.handler
    Timeout: 30
    Tracing: Active
    Environment:
      Variables:
        ENVIRONMENT: !Ref Environment
        EVENT_BUS_NAME: !Ref EventBusName
        METADATA_KEY: "__metadata"
        TABLE_NAME: !Ref Table
        POWERTOOLS_SERVICE_NAME: warehouse
        POWERTOOLS_TRACE_DISABLED: "false"
        LOG_LEVEL: !Ref LogLevel
    Layers:
      - !Sub "arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension-Arm64:1"


Resources:
  #########
  # TABLE #
  #########

  Table:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: orderId
          AttributeType: S
        - AttributeName: productId
          AttributeType: S
        - AttributeName: newDate
          AttributeType: S
      BillingMode: PAY_PER_REQUEST
      KeySchema:
        - AttributeName: orderId
          KeyType: HASH
        - AttributeName: productId
          KeyType: RANGE
      GlobalSecondaryIndexes:
        - IndexName: product
          KeySchema:
            - AttributeName: productId
              KeyType: HASH
            - AttributeName: orderId
              KeyType: RANGE
          Projection:
            ProjectionType: ALL
        - IndexName: orderId-new
          KeySchema:
            - AttributeName: orderId
              KeyType: HASH
            - AttributeName: newDate
              KeyType: RANGE
          Projection:
            ProjectionType: ALL
      StreamSpecification:
        StreamViewType: NEW_AND_OLD_IMAGES

  TableParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Sub /ecommerce/${Environment}/warehouse/table/name
      Type: String
      Value: !Ref Table

  #############
  # FUNCTIONS #
  #############

  OnOrderEventsFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/on_order_events/
      Events:
        OrdersCreatedOrDeleted:
          Type: CloudWatchEvent
          Properties:
            EventBusName: !Ref EventBusName
            Pattern:
              # Capture Created and Deleted events
              source: [ecommerce.orders]
              detail-type:
                - OrderCreated
                - OrderDeleted
        OrdersModified:
          Type: CloudWatchEvent
          Properties:
            EventBusName: !Ref EventBusName
            Pattern:
              # Capture Modified events if the products have changed
              source: [ecommerce.orders]
              detail-type:
                - OrderModified
              detail:
                changed: [products]
      EventInvokeConfig:
        # Put failed events on a DLQ
        DestinationConfig:
          OnFailure:
            Type: SQS
            Destination: !GetAtt DeadLetterQueue.Outputs.QueueArn
      Policies:
        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy
        - DynamoDBCrudPolicy:
            TableName: !Ref Table

  OnOrderEventsLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/lambda/${OnOrderEventsFunction}"
      RetentionInDays: !Ref RetentionInDays

  TableUpdateFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: main.handler
      CodeUri: src/table_update/
      Events:
        DynamoDB:
          Type: DynamoDB
          Properties:
            Stream: !GetAtt Table.StreamArn
            StartingPosition: TRIM_HORIZON
            DestinationConfig:
              OnFailure:
                Destination: !GetAtt DeadLetterQueue.Outputs.QueueArn
      Policies:
        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy
        - Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - events:PutEvents
              Resource: !Ref EventBusArn
              Condition:
                StringEquals:
                  events:source: "ecommerce.warehouse"
            - Effect: Allow
              Action:
                - dynamodb:Query
              Resource: !GetAtt Table.Arn
            - Effect: Allow
              Action:
                - sqs:SendMessage
              Resource: !GetAtt DeadLetterQueue.Outputs.QueueArn

  TableUpdateLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/aws/lambda/${TableUpdateFunction}"
      RetentionInDays: !Ref RetentionInDays

  #####################
  # DEAD LETTER QUEUE #
  #####################
  DeadLetterQueue:
    Type: AWS::CloudFormation::Stack
    Properties:
      # The path starts with '../..' as this will be evaluated from the
      # warehouse/build folder, not the warehouse folder.
      TemplateURL: ../../shared/templates/dlq.yaml


  #############
  # DASHBOARD #
  #############
  Dashboard:
    Type: AWS::CloudWatch::Dashboard
    Properties:
      DashboardName: !Ref AWS::StackName
      DashboardBody: !Sub |
        {
          "start": "-PT6H",
          "periodOverride": "inherit",
          "widgets": [
            {
              "type": "text",
              "x": 0,
              "y": 0,
              "width": 20,
              "height": 1,
              "properties": {
                "markdown": "\n# Key Metrics\n"
              }
            },
            {
              "type": "metric",
              "x": 0,
              "y": 1,
              "width": 12,
              "height": 3,
              "properties": {
                "metrics": [
                  ["ecommerce.warehouse", "packageCreated", "environment", "${Environment}", "service", "warehouse", {"color": "#66bb6a", "label": "Package Created"}],
                  ["ecommerce.warehouse", "packagingFailed", "environment", "${Environment}", "service", "warehouse", {"color": "#ef5350", "label": "Packaging Failed"}]
                ],
                "view": "singleValue",
                "period": 86400,
                "stacked": false,
                "stat": "Sum",
                "region": "${AWS::Region}",
                "title": "Packages"
              }
            },
            {
              "type": "metric",
              "x": 12,
              "y": 1,
              "width": 12,
              "height": 3,
              "properties": {
                "metrics": [
                  [{"expression": "le1+le2+lt1+lt2", "label": "Lambda Errors", "color": "#66bb6a"}],
                  [{"expression": "db1+db2", "label": "DynamoDB Errors", "color": "#ffa726"}],

                  ["AWS/Lambda", "Errors", "FunctionName", "${OnOrderEventsFunction}", {"id": "le1", "visible": false}],
                  ["AWS/Lambda", "Errors", "FunctionName", "${TableUpdateFunction}", {"id": "le2", "visible": false}],

                  ["AWS/Lambda", "Throttles", "FunctionName", "${OnOrderEventsFunction}", {"id": "lt1", "visible": false}],
                  ["AWS/Lambda", "Throttles", "FunctionName", "${TableUpdateFunction}", {"id": "lt2", "visible": false}],

                  ["AWS/DynamoDB", "UserErrors", "TableName", "${Table}", {"id": "db1", "visible": false}],
                  ["AWS/DynamoDB", "SystemErrors", "TableName", "${Table}", {"id": "db2", "visible": false}]
                ],
                "view": "singleValue",
                "period": 86400,
                "stacked": false,
                "stat": "Sum",
                "region": "${AWS::Region}",
                "title": "Errors"
              }
            },
            {
              "type": "text",
              "x": 0,
              "y": 7,
              "width": 24,
              "height": 1,
              "properties": {
                "markdown": "\n# Traffic\n"
              }
            },
            {
              "type": "metric",
              "x": 0,
              "y": 8,
              "width": 12,
              "height": 6,
              "properties": {
                "metrics": [
                  ["AWS/Lambda", "Invocations", "FunctionName", "${OnOrderEventsFunction}", {"color": "#29b6f6", "label": "OnOrderEvents"}],
                  ["AWS/Lambda", "Invocations", "FunctionName", "${TableUpdateFunction}", {"color": "#ffa726", "label": "TableUpdate"}]
                ],
                "view": "timeSeries",
                "period": 60,
                "stacked": false,
                "stat": "Sum",
                "region": "${AWS::Region}",
                "title": "Requests"
              }
            },
            {
              "type": "metric",
              "x": 12,
              "y": 8,
              "width": 12,
              "height": 6,
              "properties": {
                "metrics": [
                  ["ecommerce.warehouse", "packageCreated", "environment", "${Environment}", "service", "warehouse", {"color": "#66bb6a", "label": "Package Created"}],
                  ["ecommerce.warehouse", "packagingFailed", "environment", "${Environment}", "service", "warehouse", {"color": "#ef5350", "label": "Packaging Failed"}]
                ],
                "view": "timeSeries",
                "period": 60,
                "stacked": false,
                "stat": "Sum",
                "region": "${AWS::Region}",
                "title": "Packages"
              }
            },
            {
              "type": "text",
              "x": 0,
              "y": 14,
              "width": 24,
              "height": 1,
              "properties": {
                "markdown": "\n# Latency and Duration\n"
              }
            },
            {
              "type": "metric",
              "x": 0,
              "y": 15,
              "width": 10,
              "height": 3,
              "properties": {
                "metrics": [
                  ["AWS/Lambda", "Duration", "FunctionName", "${OnOrderEventsFunction}", {"color": "#9ccc65", "label": "p50"}],
                  ["...", {"stat": "p90", "color": "#ffee58", "label": "p90"}],
                  ["...", {"stat": "p99", "color": "#ef5350", "label": "p99"}]
                ],
                "view": "singleValue",
                "period": 86400,
                "stacked": false,
                "stat": "p50",
                "region": "${AWS::Region}",
                "title": "OnOrderEvents Latency"
              }
            },
            {
              "type": "metric",
              "x": 10,
              "y": 15,
              "width": 10,
              "height": 3,
              "properties": {
                "metrics": [
                  ["AWS/Lambda", "Duration", "FunctionName", "${TableUpdateFunction}", {"color": "#9ccc65", "label": "p50"}],
                  ["...", {"stat": "p90", "color": "#ffee58", "label": "p90"}],
                  ["...", {"stat": "p99", "color": "#ef5350", "label": "p99"}]
                ],
                "view": "singleValue",
                "period": 86400,
                "stacked": false,
                "stat": "p50",
                "region": "${AWS::Region}",
                "title": "TableUpdate Latency"
              }
            },
            {
              "type": "metric",
              "x": 0,
              "y": 21,
              "width": 10,
              "height": 6,
              "properties": {
                "metrics": [
                  ["AWS/Lambda", "Duration", "FunctionName", "${OnOrderEventsFunction}", {"color": "#9ccc65", "label": "p50"}],
                  ["...", {"stat": "p90", "color": "#ffee58", "label": "p90"}],
                  ["...", {"stat": "p99", "color": "#ef5350", "label": "p99"}]
                ],
                "view": "timeSeries",
                "period": 60,
                "stacked": false,
                "stat": "p50",
                "region": "${AWS::Region}",
                "title": "OnOrderEvents Duration"
              }
            },
            {
              "type": "metric",
              "x": 10,
              "y": 21,
              "width": 10,
              "height": 6,
              "properties": {
                "metrics": [
                  ["AWS/Lambda", "Duration", "FunctionName", "${TableUpdateFunction}", {"color": "#9ccc65", "label": "p50"}],
                  ["...", {"stat": "p90", "color": "#ffee58", "label": "p90"}],
                  ["...", {"stat": "p99", "color": "#ef5350", "label": "p99"}]
                ],
                "view": "timeSeries",
                "period": 60,
                "stacked": false,
                "stat": "p50",
                "region": "${AWS::Region}",
                "title": "TableUpdate Duration"
              }
            },
            {
              "type": "metric",
              "x": 0,
              "y": 22,
              "width": 12,
              "height": 6,
              "properties": {
                "metrics": [
                  ["AWS/DynamoDB", "SuccessfulRequestLatency", "TableName", "${Table}", "Operation", "GetItem", {"color": "#66bb6a", "label": "GetItem"}],
                  ["AWS/DynamoDB", "SuccessfulRequestLatency", "TableName", "${Table}", "Operation", "PutItem", {"color": "#ffa726", "label": "PutItem"}],
                  ["AWS/DynamoDB", "SuccessfulRequestLatency", "TableName", "${Table}", "Operation", "UpdateItem", {"color": "#29b6f6", "label": "UpdateItem"}],
                  ["AWS/DynamoDB", "SuccessfulRequestLatency", "TableName", "${Table}", "Operation", "DeleteItem", {"color": "#ef5350", "label": "DeleteItem"}],
                  ["AWS/DynamoDB", "SuccessfulRequestLatency", "TableName", "${Table}", "Operation", "Query", {"color": "#ec407a", "label": "Query"}]
                ],
                "view": "timeSeries",
                "period": 60,
                "stacked": false,
                "stat": "Average",
                "region": "${AWS::Region}",
                "title": "DynamoDB Latency"
              }
            },
            {
              "type": "metric",
              "x": 12,
              "y": 22,
              "width": 12,
              "height": 6,
              "properties": {
                "metrics": [
                  ["AWS/Lambda", "Duration", "FunctionName", "${OnOrderEventsFunction}", {"color": "#ec407a", "label": "OnOrderEvents P90"}],
                  ["AWS/Lambda", "Duration", "FunctionName", "${TableUpdateFunction}", {"color": "#ffa726", "label": "TableUpdate P90"}],
                  ["AWS/Lambda", "IteratorAge", "FunctionName", "${TableUpdateFunction}", {"color": "#ff7043", "stat": "Average", "label": "TableUpdate Iterator Age"}]
                ],
                "view": "timeSeries",
                "period": 60,
                "stacked": false,
                "stat": "p90",
                "region": "${AWS::Region}",
                "title": "Asynchronous Durations"
              }
            },
            {
              "type": "text",
              "x": 0,
              "y": 23,
              "width": 24,
              "height": 1,
              "properties": {
                "markdown": "\n# Errors\n"
              }
            },
            {
              "type": "metric",
              "x": 0,
              "y": 24,
              "width": 8,
              "height": 6,
              "properties": {
                "metrics": [
                  ["AWS/Lambda", "Errors", "FunctionName", "${OnOrderEventsFunction}", {"color": "#29b6f6", "label": "OnOrderEvents"}],
                  ["AWS/Lambda", "Errors", "FunctionName", "${TableUpdateFunction}", {"color": "#ffa726", "label": "TableUpdate"}]
                ],
                "view": "timeSeries",
                "period": 60,
                "stacked": false,
                "stat": "Sum",
                "region": "${AWS::Region}",
                "title": "Lambda Errors"
              }
            },
            {
              "type": "metric",
              "x": 8,
              "y": 24,
              "width": 8,
              "height": 6,
              "properties": {
                "metrics": [
                  ["AWS/DynamoDB", "UserErrors", "TableName", "${Table}", {"color": "#ffa726", "label": "User Errors"}],
                  ["AWS/DynamoDB", "SystemErrors", "TableName", "${Table}", {"color": "#ef5350", "label": "System Errors"}]
                ],
                "view": "timeSeries",
                "period": 60,
                "stacked": false,
                "stat": "Sum",
                "region": "${AWS::Region}",
                "title": "DynamoDB Errors"
              }
            },

            {
              "type": "log",
              "x": 0,
              "y": 29,
              "width": 24,
              "height": 6,
              "properties": {
                "query": "SOURCE '/aws/lambda/${OnOrderEventsFunction}' | SOURCE '/aws/lambda/${TableUpdateFunction}' | fields @message \n | filter @message like /ERROR/ \n | sort @timestamp desc\n| limit 20\n",
                "region": "${AWS::Region}",
                "stacked": false,
                "view": "table",
                "title": "Last 20 errors"
              }
            }
          ]
        }