"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SummaryDashboard = void 0;
const aws_cdk_lib_1 = require("aws-cdk-lib");
const aws_appsync_1 = require("aws-cdk-lib/aws-appsync");
const metrics_1 = require("../metrics");
class SummaryDashboard extends aws_cdk_lib_1.aws_cloudwatch.Dashboard {
    /**
     * Creates a dashboard with debug/informational metrics for an API Gateway.
     * It will create widgets to track requests, errors and latencies for the
     * API, all of its methods, and any lambda integrations. Assumes that method
     * names and lambda names begin with the api name.
     */
    constructor(scope, id, props) {
        const services = props.services || [];
        const lambdaNames = props.lambdaNames || [];
        const requestMetrics = [];
        const errorMetrics = [];
        const latencies = [];
        services.forEach((service, i) => {
            switch (service.typeName) {
                case 'ElasticSearchApi':
                    const esProps = { ...service, metricIdx: i };
                    requestMetrics.push(new metrics_1.ElasticSearchApiRequestRateMetric(esProps));
                    errorMetrics.push(new metrics_1.ElasticSearchApiErrorRatesMetric(esProps).rate5xx);
                    latencies.push(new metrics_1.ElasticSearchApiLatencyMetric(esProps, 95));
                    break;
                case 'GraphqlApi':
                    const gqlProps = { ...service, metricIdx: i };
                    requestMetrics.push(new metrics_1.GraphqlApiRequestRateMetric(gqlProps));
                    errorMetrics.push(new metrics_1.GraphqlApiErrorRatesMetric(gqlProps).rate5xx);
                    latencies.push(new metrics_1.GraphqlApiLatencyMetric(gqlProps, 95));
                    break;
                case 'APIGateway':
                    const apiProps = { ...service, metricIdx: i };
                    requestMetrics.push(new metrics_1.ApiGatewayRequestRateMetric(apiProps));
                    errorMetrics.push(new metrics_1.ApiGatewayErrorRatesMetric(apiProps).rate5xx);
                    latencies.push(new metrics_1.ApiGatewayLatencyMetric(apiProps, 95));
                    break;
                case 'Cloudfront':
                    const cfProps = { ...service, metricIdx: i };
                    requestMetrics.push(new metrics_1.CloudfrontRequestRateMetric(cfProps));
                    errorMetrics.push(new metrics_1.CloudfrontErrorRatesMetric(cfProps).rate5xx);
                    latencies.push(new metrics_1.CloudfrontLatencyMetric(cfProps, 95));
                    break;
                default:
                    throw new Error(`Unexpected service type when generating SummaryDashboard. Service: ${service}`);
            }
        });
        const trafficPerService = new aws_cdk_lib_1.aws_cloudwatch.GraphWidget({
            title: 'Traffic per Service',
            width: 24,
            height: 4,
            left: requestMetrics,
            leftYAxis: { label: 'Requests/s', showUnits: false, min: 0 },
            legendPosition: aws_cdk_lib_1.aws_cloudwatch.LegendPosition.BOTTOM,
        });
        const errorsPerService = new aws_cdk_lib_1.aws_cloudwatch.GraphWidget({
            title: 'Errors per Service',
            width: 24,
            height: 4,
            left: errorMetrics,
            legendPosition: aws_cdk_lib_1.aws_cloudwatch.LegendPosition.BOTTOM,
            leftYAxis: { label: 'Percent', showUnits: false, min: 0, max: 100 },
        });
        const latenciesPerService = new aws_cdk_lib_1.aws_cloudwatch.GraphWidget({
            title: 'Latencies per Service',
            width: 24,
            height: 4,
            left: latencies,
            legendPosition: aws_cdk_lib_1.aws_cloudwatch.LegendPosition.BOTTOM,
            leftYAxis: { label: '', showUnits: true },
        });
        const widgets = [
            [trafficPerService],
            [errorsPerService],
            [latenciesPerService],
        ];
        if (lambdaNames.length > 0) {
            const concurrentExecutions = lambdaNames.map((lambda) => new metrics_1.LambdaSearchConcurrencyMetric({ lambdaSearchName: lambda }));
            const lambdaSaturations = new aws_cdk_lib_1.aws_cloudwatch.GraphWidget({
                title: 'Lambda Saturation',
                width: 24,
                height: 4,
                left: concurrentExecutions,
                legendPosition: aws_cdk_lib_1.aws_cloudwatch.LegendPosition.HIDDEN,
                leftAnnotations: [{ label: 'Concurrency Limit', value: 1000 }],
                leftYAxis: { label: 'Concurrent Executions', showUnits: false },
            });
            widgets.push([lambdaSaturations]);
            const durationsLeftAnnotations = props.hideLambdaIntegrationTimeoutAnnotation ? [] :
                [{ label: 'Integration Timeout', value: 30000 }];
            const lambdaDurations = new aws_cdk_lib_1.aws_cloudwatch.GraphWidget({
                title: 'Lambda Duration',
                width: 24,
                height: 4,
                left: lambdaNames.map((lambda) => new metrics_1.LambdaSearchDurationsMetric({ lambdaSearchName: lambda })),
                legendPosition: aws_cdk_lib_1.aws_cloudwatch.LegendPosition.HIDDEN,
                leftAnnotations: durationsLeftAnnotations,
                leftYAxis: { label: 'Milliseconds', showUnits: false },
            });
            widgets.push([lambdaDurations]);
        }
        super(scope, id, { ...{ widgets }, ...props });
    }
    /**
     * Constructs a SummaryDashboard by searching the tree for known resources. You can optionally still provide
     * a starting list of services to build on, but note that this will not deduplicate, so only do this if
     * this method misses a service.
     */
    static fromTree(scope, id, props) {
        var _a, _b, _c, _d, _e;
        const result = SummaryDashboard.traverse(props.discoverRoot, (_a = props.services) !== null && _a !== void 0 ? _a : [], (_b = props.lambdaNames) !== null && _b !== void 0 ? _b : [], (_c = props.excludeStacks) !== null && _c !== void 0 ? _c : [], (_d = props.includeLambdas) !== null && _d !== void 0 ? _d : true, (_e = props.serviceLabels) !== null && _e !== void 0 ? _e : {});
        const mergedProps = { ...props, ...result };
        return new SummaryDashboard(scope, id, mergedProps);
    }
    static visit(node, services, lambdaNames, includeLambdas, serviceLabels) {
        const customLabel = serviceLabels[node.node.addr];
        if (node instanceof aws_cdk_lib_1.aws_opensearchservice.CfnDomain) {
            if (node.domainName != undefined) {
                services.push({
                    typeName: 'ElasticSearchApi',
                    accountId: aws_cdk_lib_1.Stack.of(node).account,
                    domainName: node.domainName,
                    label: customLabel !== null && customLabel !== void 0 ? customLabel : node.domainName,
                });
            }
        }
        if (node instanceof aws_appsync_1.GraphqlApi) {
            services.push({
                typeName: 'GraphqlApi',
                apiId: node.apiId,
                label: customLabel !== null && customLabel !== void 0 ? customLabel : node.name,
            });
        }
        if (node instanceof aws_cdk_lib_1.aws_apigateway.RestApi) {
            services.push({
                typeName: 'APIGateway',
                apiName: node.restApiName,
                // TODO: Change this to import the stage name
                stage: 'prod', // node.deploymentStage.stageName,
                label: customLabel !== null && customLabel !== void 0 ? customLabel : node.restApiName,
            });
        }
        if (node instanceof aws_cdk_lib_1.aws_cloudfront.Distribution) {
            services.push({
                typeName: 'Cloudfront',
                distributionId: node.distributionId,
                label: customLabel !== null && customLabel !== void 0 ? customLabel : node.distributionId,
            });
        }
        if (includeLambdas && node instanceof aws_cdk_lib_1.aws_lambda.Function) {
            lambdaNames.push(node.functionName);
        }
    }
    static traverse(root, startingServices, startingLambdaNames, excludeStacks, includeLambdas, serviceLabels) {
        const foundResources = { services: startingServices, lambdaNames: startingLambdaNames };
        const recurse = (construct, services, lambdaNames) => {
            if (construct instanceof aws_cdk_lib_1.Stack && excludeStacks.includes(construct.stackName)) {
                console.log(`Skipping stack ${construct.stackName}`);
                return;
            }
            SummaryDashboard.visit(construct, services, lambdaNames, includeLambdas, serviceLabels);
            for (const child of construct.node.children) {
                if (!aws_cdk_lib_1.Stage.isStage(child)) {
                    recurse(child, services, lambdaNames);
                }
            }
        };
        recurse(root, startingServices, startingLambdaNames);
        return foundResources;
    }
}
exports.SummaryDashboard = SummaryDashboard;
