Skip to content

Strange error when filtering createdAt with a sort key #3331

@Lychas

Description

@Lychas

Environment information

System:
  OS: Windows 11 10.0.26100
  CPU: (28) x64 Intel(R) Core(TM) i7-14700KF
  Memory: 19.17 GB / 31.83 GB
Binaries:
  Node: 22.16.0 - C:\Program Files\nodejs\node.EXE
  Yarn: undefined - undefined
  npm: 11.4.2 - C:\Program Files\nodejs\npm.CMD
  pnpm: undefined - undefined
NPM Packages:
  @aws-amplify/auth-construct: 1.8.1
  @aws-amplify/backend: 1.16.1
  @aws-amplify/backend-ai: Not Found
  @aws-amplify/backend-auth: 1.7.1
  @aws-amplify/backend-cli: 1.8.0
  @aws-amplify/backend-data: 1.6.1
  @aws-amplify/backend-deployer: 2.1.3
  @aws-amplify/backend-function: 1.14.1
  @aws-amplify/backend-output-schemas: 1.7.0
  @aws-amplify/backend-output-storage: 1.3.1
  @aws-amplify/backend-secret: 1.4.0
  @aws-amplify/backend-storage: 1.4.1
  @aws-amplify/cli-core: 2.2.1
  @aws-amplify/client-config: 1.8.0
  @aws-amplify/data-construct: 1.16.1
  @aws-amplify/data-schema: 1.21.0
  @aws-amplify/deployed-backend-client: 1.8.0
  @aws-amplify/form-generator: 1.2.1
  @aws-amplify/model-generator: 1.2.0
  @aws-amplify/platform-core: 1.10.0
  @aws-amplify/plugin-types: 1.11.0
  @aws-amplify/sandbox: 2.1.2
  @aws-amplify/schema-generator: 1.4.0
  @aws-cdk/toolkit-lib: 1.1.1
  aws-amplify: 6.15.5
  aws-cdk-lib: 2.210.0
  typescript: 5.8.3
npm warn exec The following package was not found and will be installed: [email protected]
No AWS environment variables
No CDK environment variables

Describe the bug

Hello there, I'm getting an error when filtering on createdAt while using a sort key to filter the data from GraphQl

This is the error from CloudWatch when running my function:

ERROR	Invoke Error 	
{
    "errorType": "Error",
    "errorMessage": "[{\"path\":[\"listActivityLogBySortKeyAndCreatedAt\"],\"data\":null,\"errorType\":\"DynamoDB:DynamoDbException\",\"errorInfo\":null,\"locations\":[{\"line\":9,\"column\":3,\"sourceName\":null}],\"message\":\"Invalid KeyConditionExpression: KeyConditionExpressions must only contain one condition per key (Service: DynamoDb, Status Code: 400, Request ID: 6O2VNKB86T6N7NKLFC2O8JV1ORVV4KQNSO5AEMVJF66Q9ASUAAJG) (SDK Attempt Count: 1)\"}]",
    "stack": [
        "Error: [{\"path\":[\"listActivityLogBySortKeyAndCreatedAt\"],\"data\":null,\"errorType\":\"DynamoDB:DynamoDbException\",\"errorInfo\":null,\"locations\":[{\"line\":9,\"column\":3,\"sourceName\":null}],\"message\":\"Invalid KeyConditionExpression: KeyConditionExpressions must only contain one condition per key (Service: DynamoDb, Status Code: 400, Request ID: 6O2VNKB86T6N7NKLFC2O8JV1ORVV4KQNSO5AEMVJF66Q9ASUAAJG) (SDK Attempt Count: 1)\"}]",
        "    at vP (file:///var/task/index.mjs:573:1821)",
        "    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)",
        "    at async Promise.all (index 0)",
        "    at async Runtime.NAn [as handler] (file:///var/task/index.mjs:573:1199)"
    ]
}

Whenever I pass any createAt filter to any query with "list(x)BySortKeyAndCreatedAt)" it returns this error and I can't understand it or why it's happening.

Any other table that gets createdAt filter that doesn't use a sortKey works great but if I even dare to put any filter on the createdAt on "list(x)BySortKeyAndCreatedAt) then it returns nothing, this error is shared throught the Amplify app with expo and on lambda functions with amplify.

Reproduction steps

this is my Schema:

import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
import { calculateAgenciesMonthly } from "../functions/calculateAgenciesMonthly/resource";
import { checkIfExcelDataValid } from "../functions/checkIfExcelDataValid/resource";
import { createActivityLog } from "../functions/createActivityLog/resource";
import { createExcel } from "../functions/createExcel/resource";
import { createNewTask } from "../functions/createNewTask/resource";
import { createUser } from "../functions/createUser/resource";
import { deleteUser } from "../functions/deleteUser/resource";
import { editTask } from "../functions/editTask/resource";
import { editUser } from "../functions/editUser/resource";
import { getFullUser } from "../functions/getFullUser/resource";
import { getMassImportTemplate } from "../functions/getMassImportTemplate/resource";
import { getNewPackage } from "../functions/getNewPackage/resource";
import { massImport } from "../functions/massImport/resource";

const schema = a
    .schema({
        // models
        Employee: a
            .model({
                id: a.string().required(),
                name: a.string().required(),
                department: a.ref("Departments").required(),
                salary: a
                    .integer()
                    .required()
                    .validate((value) => value.gt(0)),
                expectedHours: a
                    .integer()
                    .required()
                    .validate((value) => value.gt(0)),
                tasks: a.hasMany("Task", "employeeId"),
                activityLogs: a.hasMany("ActivityLog", "employeeId"),
            })
            .disableOperations(["subscriptions"])
            .authorization((allow) => [
                allow.ownerDefinedIn("id").to(["read"]),
                allow.group("MANAGEMENT").to(["read"]),
            ]),

        Task: a
            .model({
                id: a.id().required(),
                title: a.string().required(),
                // start: a.datetime().required(),
                // end: a.datetime().required(),
                agency: a.ref("Agencies").required(),

                totalHours: a
                    .float()
                    .required()
                    .validate((value) => value.gt(0)),
                hourlyValue: a
                    .float()
                    .required()
                    .validate((value) => value.gt(0)),
                salaryAtTime: a
                    .float()
                    .required()
                    .validate((value) => value.gt(0)),
                taskValue: a
                    .float()
                    .required()
                    .validate((value) => value.gt(0)),
                projectNumber: a.string(),
                invoiceNumber: a.string(),
                taskDate: a.datetime().required(),
                sortKey: a.ref("SortKey").required(),
                createdAt: a.datetime().required(),
                governorate: a.ref("IraqGovernorates").required(),
                outsideIraqLocation: a.string(),

                employeeId: a.id().required(),
                employee: a.belongsTo("Employee", "employeeId"),

                customerNumber: a.id().required(),
                customer: a.belongsTo("Customer", "customerNumber"),
            })
            .disableOperations(["subscriptions"])
            .secondaryIndexes((index) => [
                index("employeeId").sortKeys(["createdAt"]),
                index("sortKey").sortKeys(["createdAt"]),
            ])
            .authorization((allow) => [
                allow.ownerDefinedIn("employeeId").to(["read"]),
                allow.group("MANAGEMENT"),
            ]),

        // This model calculates the total value and time spent on tasks for each agency monthly.
        // It is used for reporting and analytics purposes.
        AgencyTasks: a
            .model({
                id: a.id().required(),
                // from agency enums
                beckmanCost: a.float().required(),
                beckmanHours: a.float().required(),
                leicaCost: a.float().required(),
                leicaHours: a.float().required(),
                bioRadCost: a.float().required(),
                bioRadHours: a.float().required(),
                schmitzCost: a.float().required(),
                schmitzHours: a.float().required(),
                dexisCost: a.float().required(),
                dexisHours: a.float().required(),
                bioronCost: a.float().required(),
                bioronHours: a.float().required(),
                spectroCost: a.float().required(),
                spectroHours: a.float().required(),
                molecularCost: a.float().required(),
                molecularHours: a.float().required(),
                hqProjectCost: a.float().required(),
                hqProjectHours: a.float().required(),
                otherCost: a.float().required(),
                otherHours: a.float().required(),

                sortKey: a.ref("SortKey").required(),

                createdAt: a.datetime().required(),

                totalCost: a.float().required(),
                totalHours: a.float().required(),
            })
            .disableOperations(["subscriptions"])
            .secondaryIndexes((index) => [index("sortKey").sortKeys(["createdAt"])])
            .authorization((allow) => [allow.group("MANAGEMENT")]),

        Customer: a
            .model({
                name: a.string().required(),
                number: a.string().required(),
                city: a.string().required(),
                address: a.string(),
                createdAt: a.datetime(),

                tasks: a.hasMany("Task", "customerNumber"),
            })
            .disableOperations(["subscriptions"])
            .authorization((allow) => [
                allow.authenticated().to(["read"]),
                allow.group("MANAGEMENT"),
            ])
            .identifier(["number"]),

        ActivityLog: a
            .model({
                id: a.id().required(),
                action: a.ref("ActivityLogActions").required(),
                description: a.string().required(),
                createdAt: a.datetime().required(),
                sortKey: a.ref("SortKey").required(),
                employeeId: a.id().required(),
                employee: a.belongsTo("Employee", "employeeId"),
            })
            .disableOperations(["subscriptions"])
            .authorization((allow) => [allow.group("MANAGEMENT").to(["read"])])
            .secondaryIndexes((index) => [index("sortKey").sortKeys(["createdAt"])]),

        //ServiceHub
        ServiceHubUnAssignedTask: a
            .model({
                id: a.id().required(),

                type: a.string().required(),
                status: a.string().required(),
                description: a.string().required(),

                sortKey: a.ref("SortKey").required(),
                createdAt: a.datetime().required(),
            })
            .disableOperations(["subscriptions"])
            .secondaryIndexes((index) => [index("sortKey").sortKeys(["createdAt"])])
            .authorization((allow) => [
                allow.group("MANAGEMENT"),
                allow.group("SERVICE").to(["create"]),
                allow.group("SERVICEMANAGER"),
            ]),

        ServiceHubActiveTask: a
            .model({
                id: a.id().required(),
                type: a.string().required(),
                status: a.string().required(),
                description: a.string().required(),

                dueDate: a.datetime(),
                startDate: a.datetime(),
                assignedAt: a.datetime(),
                requestedAt: a.datetime(),

                sortKey: a.ref("SortKey").required(),
                createdAt: a.datetime().required(),
            })
            .disableOperations(["subscriptions"])
            .authorization((allow) => [allow.group("MANAGEMENT"), allow.group("SERVICEMANAGER")])
            .secondaryIndexes((index) => [index("sortKey").sortKeys(["createdAt"])]),

        ServiceHubFinishedTask: a
            .model({
                id: a.id().required(),
                type: a.string().required(),
                startDate: a.datetime().required(),
                endDate: a.datetime().required(),
                description: a.string().required(),

                parts: a.ref("serviceHubPart").array(),
                isPartReplaced: a.boolean().required(),
                comment: a.string().required(),

                beforePhotos: a.ref("photo").array(),
                afterPhotos: a.ref("photo").array(),

                personInCharge: a.string().required(),
                personInChargePhoneNumber: a.phone().required(),
                peopleTrained: a.string(),
                feedback: a.ref("feedback"),

                customerSignature: a.ref("photo").required(),
                engineerSignature: a.ref("photo").required(),

                assignedAt: a.datetime().required(),
                requestedAt: a.datetime().required(),
                status: a.string().required(),

                finishingEngineer: a.string().required(),

                sortKey: a.ref("SortKey").required(),
                createdAt: a.datetime().required(),
            })
            .disableOperations(["subscriptions"])
            .secondaryIndexes((index) => [index("sortKey").sortKeys(["createdAt"])])
            .authorization((allow) => [allow.group("MANAGEMENT"), allow.group("SERVICEMANAGER")]),

        ServiceHubDevice: a
            .model({
                id: a.id().required(),
                model: a.string().required(),
                brand: a.string().required(),
                serialNumber: a.string().required(),

                installationDate: a.datetime(),
                lastService: a.datetime(),
                isDeviceActive: a.boolean(),

                remarks: a.string().required(),
                location: a.string().required(),

                orderNumber: a.string().required(),
                city: a.string().required(),

                deviceHistory: a.ref("deviceHistory").array(),
            })
            .disableOperations(["subscriptions"])
            .authorization((allow) => [
                allow.group("MANAGEMENT"),
                allow.group("SERVICEMANAGER"),
                allow.ownerDefinedIn("employeeId").to(["read"]),
            ]),

        // functions
        createUser: a
            .mutation()
            .arguments({
                email: a.email().required(),
                givenName: a.string().required(),
                isManagement: a.boolean().required(),
                isServiceManager: a.boolean().required(),
                salary: a.integer().required(),
                department: a.ref("Departments").required(),
                expectedHours: a.integer().required(),
            })
            .returns(a.boolean().required())
            .authorization((allow) => [allow.group("MANAGEMENT")])
            .handler(a.handler.function(createUser)),

        editUser: a
            .mutation()
            .arguments({
                id: a.id().required(),
                expectedHours: a.integer(),
                email: a.email(),
                givenName: a.string(),
                managementStatusChange: a.ref("ManagementStatusChange"),
                userStatusChange: a.ref("UserStatusChange"),
                serviceStatusChange: a.ref("ServiceStatusChange"),
                serviceManagerStatusChange: a.ref("ServiceManagerStatusChange"),
                salary: a.integer(),
                department: a.ref("Departments"),
            })
            .returns(a.boolean().required())
            .authorization((allow) => [allow.group("MANAGEMENT")])
            .handler(a.handler.function(editUser)),

        deleteUser: a
            .mutation()
            .arguments({
                id: a.id().required(),
            })
            .returns(a.boolean().required())
            .authorization((allow) => [allow.group("MANAGEMENT")])
            .handler(a.handler.function(deleteUser)),

        getFullUser: a
            .query()
            .arguments({
                id: a.id().required(),
            })
            .returns(a.ref("getFullUserResponse"))
            .authorization((allow) => [allow.group("MANAGEMENT")])
            .handler(a.handler.function(getFullUser)),

        createNewTask: a
            .mutation()
            .arguments({
                totalHours: a.float().required(),
                title: a.string().required(),
                agency: a.ref("Agencies").required(),
                projectNumber: a.string(),
                invoiceNumber: a.string(),
                taskDate: a.datetime(),
                governorate: a.ref("IraqGovernorates").required(),
                outsideIraqLocation: a.string(),
                customerNumber: a.string(),
            })
            .returns(a.boolean().required())
            .authorization((allow) => [allow.authenticated()])
            .handler(a.handler.function(createNewTask)),

        editTask: a
            .mutation()
            .arguments({
                id: a.id().required(),
                totalHours: a.float(),
                title: a.string(),
                agency: a.ref("Agencies"),
                projectNumber: a.string(),
                invoiceNumber: a.string(),
                taskDate: a.datetime().required(),
                customerNumber: a.string().required(),
                governorate: a.ref("IraqGovernorates"),
                outsideIraqLocation: a.string(),
                isRemoveOutsideIraqLocation: a.boolean(),
            })
            .returns(a.boolean().required())
            .authorization((allow) => [allow.authenticated()])
            .handler(a.handler.function(editTask)),

        createExcel: a
            .query()
            .arguments({
                tables: a.ref("Tables").required().array(),
                startDate: a.datetime(),
                endDate: a.datetime(),
            })
            .returns(a.string().required())
            .authorization((allow) => [allow.group("MANAGEMENT")])
            .handler(a.handler.function(createExcel)),

        massImport: a
            .mutation()
            .arguments({
                excelPath: a.string().required(),
                isUpdateOldData: a.boolean().required(),
                importType: a.ref("MassImportTypes").required(),
            })
            .returns(a.boolean().required())
            .authorization((allow) => [allow.group("MANAGEMENT")])
            .handler(a.handler.function(massImport)),

        getMassImportTemplate: a
            .query()
            .arguments({
                template: a.ref("MassImportTemplates").required(),
            })
            .returns(a.string().required())
            .authorization((allow) => [allow.group("MANAGEMENT")])
            .handler(a.handler.function(getMassImportTemplate)),

        createActivityLogFunction: a
            .mutation()
            .arguments({
                action: a.ref("ActivityLogActions").required(),
                description: a.string().required(),
            })
            .authorization((allow) => [allow.authenticated()])
            .returns(a.boolean().required())
            .handler(a.handler.function(createActivityLog)),

        checkIfExcelDataValid: a
            .query()
            .arguments({
                importType: a.ref("MassImportTypes").required(),
                excelPath: a.string().required(),
            })
            .returns(a.ref("checkIfExcelDataValidResponse"))
            .authorization((allow) => [allow.group("MANAGEMENT")])
            .handler(a.handler.function(checkIfExcelDataValid)),
        getNewPackage: a
            .query()
            .arguments({
                version: a.string().required(),
            })
            .returns(a.ref("getNewPackageResponse"))
            .authorization((allow) => [allow.authenticated()])
            .handler(a.handler.function(getNewPackage)),

        // custom responses
        getFullUserResponse: a.customType({
            id: a.id().required(),
            name: a.string().required(),
            department: a.ref("Departments").required(),
            salary: a.integer().required(),
            expectedHours: a.integer().required(),
            email: a.email().required(),
            isManagement: a.boolean().required(),
            isServiceManager: a.boolean().required(),
            isEnabled: a.boolean().required(),
        }),

        checkIfExcelDataValidResponse: a.customType({
            isError: a.boolean().required(),
            errorMessage: a.string(),
        }),

        getNewPackageResponse: a.customType({
            isNewPackageAvailable: a.boolean().required(),
            packagePath: a.string(),
        }),

        // custom items
        serviceHubPart: a.customType({
            isPartReplaced: a.boolean().required(),
            partName: a.string().required(),
            partNumber: a.string().required(),
            quantity: a.integer().required(),
        }),

        deviceHistory: a.customType({
            customerName: a.string().required(),
            location: a.string().required(),
            startDate: a.datetime(),
            endDate: a.datetime().required(),
        }),

        photo: a.customType({
            path: a.string().required(),
            aspectRation: a.float().required(),
        }),

        feedback: a.customType({
            reliability: a.integer().required(),
            time: a.integer().required(),
            competence: a.integer().required(),
            accuracy: a.integer().required(),
            satisfaction: a.integer().required(),
            comment: a.string(),
        }),

        // enumeration
        Agencies: a.enum([
            "Beckman",
            "Leica",
            "BioRad",
            "Schmitz",
            "Dexis",
            "Bioron",
            "Spectro",
            "Molecular",
            "HqProject",
            "Other",
        ]),
        Departments: a.enum([
            "PrivateSales",
            "GovernmentSales",
            "OtherSales",
            "Management",
            "CustomerSupport",
            "Marketing",
            "Accounting",
            "TechnicalService",
            "MinistryOfHealthAffairs",
            "DebtCollection",
            "Legal",
        ]),
        IraqGovernorates: a.enum([
            "AlAnbar",
            "Babil",
            "Baghdad",
            "Basra",
            "DhiQar",
            "AlQadisiyyah",
            "Diyala",
            "Duhok",
            "Erbil",
            "Karbala",
            "Kirkuk",
            "Maysan",
            "Muthanna",
            "Najaf",
            "Nineveh",
            "SalahAlDin",
            "Sulaymaniyah",
            "Wasit",
            "Halabja",
            "OutsideIraq",
        ]),
        ManagementStatusChange: a.enum(["TO_MANAGEMENT", "REMOVE_MANAGEMENT"]),
        UserStatusChange: a.enum(["TO_ENABLE", "TO_DISABLE"]),
        ServiceStatusChange: a.enum(["TO_SERVICE", "REMOVE_SERVICE"]),
        ServiceManagerStatusChange: a.enum(["TO_SERVICEMANAGER", "REMOVE_SERVICEMANAGER"]),
        SortKey: a.enum(["sorted"]),
        Tables: a.enum(["tasks", "employees", "agencyTasks", "customers", "activityLogs"]),
        MassImportTypes: a.enum(["customers", "employees"]),
        MassImportTemplates: a.enum(["customers", "employees"]),
        ActivityLogActions: a.enum(["create", "update", "delete", "massImport", "export"]),
    })
    .authorization((allow) => [
        allow.resource(createUser),
        allow.resource(editUser),
        allow.resource(getFullUser),
        allow.resource(createNewTask),
        allow.resource(editTask),
        allow.resource(calculateAgenciesMonthly),
        allow.resource(createExcel),
        allow.resource(massImport),
        allow.resource(getMassImportTemplate),
        allow.resource(createActivityLog),
        allow.resource(checkIfExcelDataValid),
        allow.resource(getNewPackage),
        allow.resource(deleteUser),
    ]);

export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
    schema,
});

and this is one of the function that fails

import type { Handler } from "aws-lambda";

import { PutObjectCommand, PutObjectCommandInput, S3Client } from "@aws-sdk/client-s3";

import { Sha256 } from "@aws-crypto/sha256-js";
import { defaultProvider } from "@aws-sdk/credential-provider-node";
import { HttpRequest } from "@aws-sdk/protocol-http";
import { SignatureV4 } from "@aws-sdk/signature-v4";
import fetch from "node-fetch";

import ExcelJS from "exceljs";

import type {
    createdAtType,
    fetchedGraphqlContentType,
    graphqlContentType,
    graphQlVariablesType,
} from "./types";

import {
    listActivityLogBySortKeyAndCreatedAt,
    listAgencyTasksBySortKeyAndCreatedAt,
    listCustomers,
    listEmployees,
    listTaskBySortKeyAndCreatedAt,
} from "../../../types/normalDepth/queries";

import type { Schema } from "../../data/resource";

const bucketName = process.env.STORAGE_BUCKET_NAME;
const region = process.env.AWS_REGION;
const GRAPHQL_ENDPOINT = new URL(process.env.AMPLIFY_DATA_GRAPHQL_ENDPOINT!);

const client = new S3Client({ region: region });

export const handler: Handler = async (
    event,
    context
): Promise<Schema["createExcel"]["returnType"]> => {
    console.log("event", event);
    console.log("context", context);
    console.log("environment variables", process.env);

    const { tables, startDate, endDate }: Schema["createExcel"]["args"] = event.arguments;

    let createdAtFilter: createdAtType = {};

    const graphqlContent: graphqlContentType = [];
    if (startDate && endDate) {
        createdAtFilter = {
            between: [startDate, endDate],
        };
    } else if (startDate) {
        createdAtFilter = {
            gt: startDate,
        };
    } else if (endDate) {
        createdAtFilter = {
            lt: endDate,
        };
    } else {
        createdAtFilter = undefined;
    }

    if (!tables || tables.length === 0) {
        console.error("No tables provided for export");
        throw new Error("No tables provided for export");
    }

    const globalLimit = 100_000;

    tables.forEach((table) => {
        let query: string;
        let variables: graphQlVariablesType;
        switch (table) {
            case "tasks":
                query = listTaskBySortKeyAndCreatedAt;
                variables = {
                    sortKey: "sorted",
                    createdAt: createdAtFilter,
                    limit: globalLimit,
                    sortDirection: "DESC",
                };
                graphqlContent.push(
                    getGraphQlContent(query, variables, "listTaskBySortKeyAndCreatedAt")
                );
                break;
            case "employees":
                query = listEmployees;
                variables = {
                    limit: globalLimit,
                    filter: {
                        createdAt: createdAtFilter,
                    },
                };
                graphqlContent.push(getGraphQlContent(query, variables, "listEmployees"));
                break;
            case "agencyTasks":
                query = listAgencyTasksBySortKeyAndCreatedAt;
                variables = {
                    sortKey: "sorted",
                    createdAt: createdAtFilter,
                    limit: globalLimit,
                    sortDirection: "DESC",
                };
                graphqlContent.push(
                    getGraphQlContent(query, variables, "listAgencyTasksBySortKeyAndCreatedAt")
                );
                break;
            case "customers":
                query = listCustomers;
                variables = {
                    limit: globalLimit,
                    filter: {
                        createdAt: createdAtFilter,
                    },
                };
                graphqlContent.push(getGraphQlContent(query, variables, "listCustomers"));
                break;
            case "activityLogs":
                query = listActivityLogBySortKeyAndCreatedAt;
                variables = {
                    limit: globalLimit,
                    sortKey: "sorted",
                    createdAt: createdAtFilter,
                    sortDirection: "DESC",
                };
                graphqlContent.push(
                    getGraphQlContent(query, variables, "listActivityLogBySortKeyAndCreatedAt")
                );
                break;
        }
    });

    const results = await Promise.all(graphqlContent);
    const excelFile = createExcelFile(results, tables);

    const excelFileName = `exported_data_${new Date().toISOString()}.xlsx`;
    const excelFilePath = `excels/${excelFileName}`;
    const isUploaded = await uploadToS3(excelFile, excelFilePath);
    if (!isUploaded) {
        throw new Error("Failed to upload Excel file");
    }
    return excelFilePath;
};

async function getGraphQlContent(
    query: string,
    variables: graphQlVariablesType,
    queryName: string
) {
    const requestToBeSigned = new HttpRequest({
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            host: GRAPHQL_ENDPOINT.host,
        },
        hostname: GRAPHQL_ENDPOINT.host,
        body: JSON.stringify({ query: query, variables: variables }),
        path: GRAPHQL_ENDPOINT.pathname,
    });

    const signer = new SignatureV4({
        credentials: defaultProvider(),
        region: process.env.AWS_REGION!,
        service: "appsync",
        sha256: Sha256,
    });

    const signedRequest = await signer.sign(requestToBeSigned);

    const response = await fetch(GRAPHQL_ENDPOINT.toString(), {
        method: signedRequest.method,
        headers: signedRequest.headers,
        body: signedRequest.body,
    });

    const data: any = await response.json();
    if (data.errors) throw new Error(JSON.stringify(data.errors));
    return data.data[queryName].items;
}

function createExcelFile(
    graphqlContent: fetchedGraphqlContentType,
    tables: string[]
): ExcelJS.Workbook {
    const workbook = new ExcelJS.Workbook();
    workbook.creator = "Lab Consult Employees App";
    workbook.created = new Date();
    workbook.modified = new Date();
    workbook.lastModifiedBy = "Lab Consult Employees App";

    let workbookSheets: ExcelJS.Worksheet[] = [];
    graphqlContent.forEach((content, index) => {
        const sheet = workbook.addWorksheet(`Sheet ${index + 1} - ${tables[index]}`);
        workbookSheets.push(sheet);
    });

    workbook.worksheets.forEach((sheet, index) => {
        const content = graphqlContent[index];
        if (content.length === 0 || !content) {
            sheet.addRow(["No data available for this table"]);
            return;
        }

        let isHeaderSet = false;
        content.forEach((item) => {
            if (item === null || item === undefined) {
                return;
            } else if (item.__typename === "Task") {
                if (!isHeaderSet) {
                    sheet.columns = [
                        { header: "ID", key: "id", width: 20 },
                        { header: "Title", key: "title", width: 30 },
                        { header: "Employee Name", key: "employeeName", width: 30 },
                        { header: "Employee Department", key: "employeeDepartment", width: 30 },
                        { header: "Agency", key: "agency", width: 20 },
                        { header: "Total Hours", key: "totalHours", width: 15 },
                        { header: "Hourly Value", key: "hourlyValue", width: 15 },
                        { header: "Salary at Time", key: "salaryAtTime", width: 20 },
                        { header: "Project Number", key: "projectNumber", width: 20 },
                        { header: "Customer Name", key: "customerName", width: 20 },
                        { header: "Customer Number", key: "customerNumber", width: 20 },
                        { header: "Invoice Number", key: "invoiceNumber", width: 20 },
                        { header: "Task Date", key: "taskDate", width: 25 },
                        { header: "Created At", key: "createdAt", width: 25 },
                        { header: "Updated At", key: "updatedAt", width: 25 },
                    ];
                    isHeaderSet = true;
                }
                sheet.addRow({
                    id: item.id,
                    title: item.title,
                    employeeName: item.employee?.name || "N/A",
                    employeeDepartment: item.employee?.department || "N/A",
                    agency: item.agency,
                    totalHours: item.totalHours,
                    hourlyValue: item.hourlyValue,
                    salaryAtTime: item.salaryAtTime,
                    createdAt: item.createdAt,
                    updatedAt: item.updatedAt,
                    projectNumber: item.projectNumber || "N/A",
                    customerName: item.customer?.name || "N/A",
                    customerNumber: item.customer?.number || "N/A",
                    invoiceNumber: item.invoiceNumber || "N/A",
                    taskDate: item.taskDate,
                });
            } else if (item.__typename === "Employee") {
                if (!isHeaderSet) {
                    sheet.columns = [
                        { header: "ID", key: "id", width: 20 },
                        { header: "Name", key: "name", width: 30 },
                        { header: "Department", key: "department", width: 30 },
                        { header: "Expected Hours", key: "expectedHours", width: 15 },
                        { header: "Salary", key: "salary", width: 15 },
                        { header: "Created At", key: "createdAt", width: 25 },
                        { header: "Updated At", key: "updatedAt", width: 25 },
                    ];
                    isHeaderSet = true;
                }
                sheet.addRow({
                    id: item.id,
                    name: item.name,
                    department: item.department,
                    expectedHours: item.expectedHours,
                    salary: item.salary,
                    createdAt: item.createdAt,
                    updatedAt: item.updatedAt,
                });
            } else if (item.__typename === "AgencyTasks") {
                if (!isHeaderSet) {
                    sheet.columns = [
                        { header: "ID", key: "id", width: 20 },
                        { header: "Total Hours", key: "totalHours", width: 15 },
                        { header: "Total Cost", key: "totalCost", width: 15 },
                        { header: "Created At", key: "createdAt", width: 25 },
                        { header: "Updated At", key: "updatedAt", width: 25 },
                        { header: "Beckman Hours", key: "beckmanHours", width: 15 },
                        { header: "Beckman Cost", key: "beckmanCost", width: 15 },
                        { header: "BioRad Hours", key: "bioRadHours", width: 15 },
                        { header: "BioRad Cost", key: "bioRadCost", width: 15 },
                        { header: "Leica Hours", key: "leicaHours", width: 15 },
                        { header: "Leica Cost", key: "leicaCost", width: 15 },
                        { header: "Schmitz Hours", key: "schmitzHours", width: 15 },
                        { header: "Schmitz Cost", key: "schmitzCost", width: 15 },
                        { header: "Dexis Hours", key: "dexisHours", width: 15 },
                        { header: "Dexis Cost", key: "dexisCost", width: 15 },
                        { header: "Bioron Hours", key: "bioronHours", width: 15 },
                        { header: "Bioron Cost", key: "bioronCost", width: 15 },
                        { header: "Spectro Hours", key: "spectroHours", width: 15 },
                        { header: "Spectro Cost", key: "spectroCost", width: 15 },
                        { header: "Molecular Hours", key: "molecularHours", width: 15 },
                        { header: "Molecular Cost", key: "molecularCost", width: 15 },
                        { header: "HQ Project Hours", key: "hqProjectHours", width: 15 },
                        { header: "HQ Project Cost", key: "hqProjectCost", width: 15 },
                        { header: "Other Hours", key: "otherHours", width: 15 },
                        { header: "Other Cost", key: "otherCost", width: 15 },
                    ];
                    isHeaderSet = true;
                }
                sheet.addRow({
                    id: item.id,
                    totalHours: item.totalHours,
                    totalCost: item.totalCost,
                    createdAt: item.createdAt,
                    updatedAt: item.updatedAt,
                    beckmanHours: item.beckmanHours,
                    beckmanCost: item.beckmanCost,
                    bioRadHours: item.bioRadHours,
                    bioRadCost: item.bioRadCost,
                    leicaHours: item.leicaHours,
                    leicaCost: item.leicaCost,
                    schmitzHours: item.schmitzHours,
                    schmitzCost: item.schmitzCost,
                    dexisHours: item.dexisHours,
                    dexisCost: item.dexisCost,
                    bioronHours: item.bioronHours,
                    bioronCost: item.bioronCost,
                    spectroHours: item.spectroHours,
                    spectroCost: item.spectroCost,
                    molecularHours: item.molecularHours,
                    molecularCost: item.molecularCost,
                    hqProjectHours: item.hqProjectHours,
                    hqProjectCost: item.hqProjectCost,
                    otherHours: item.otherHours,
                    otherCost: item.otherCost,
                });
            } else if (item.__typename === "Customer") {
                if (!isHeaderSet) {
                    sheet.columns = [
                        { header: "Name", key: "name", width: 30 },
                        { header: "Number", key: "number", width: 20 },
                        { header: "City", key: "city", width: 20 },
                        { header: "Address", key: "address", width: 30 },
                        { header: "Created At", key: "createdAt", width: 25 },
                        { header: "Updated At", key: "updatedAt", width: 25 },
                    ];
                    isHeaderSet = true;
                }
                sheet.addRow({
                    name: item.name,
                    number: item.number,
                    city: item.city,
                    address: item.address || "N/A",
                    createdAt: item.createdAt,
                    updatedAt: item.updatedAt,
                });
            } else if (item.__typename === "ActivityLog") {
                if (!isHeaderSet) {
                    sheet.columns = [
                        { header: "ID", key: "id", width: 20 },
                        { header: "Employee", key: "employee", width: 30 },
                        { header: "Description", key: "description", width: 50 },
                        { header: "Action", key: "action", width: 30 },
                        { header: "Created At", key: "createdAt", width: 25 },
                    ];
                    isHeaderSet = true;
                }
                sheet.addRow({
                    id: item.id,
                    employee: item.employee?.name || "Error",
                    action: item.action,
                    description: item.description,
                    createdAt: item.createdAt,
                });
            }
        });
    });

    return workbook;
}

async function uploadToS3(excel: ExcelJS.Workbook, fileName: string): Promise<boolean> {
    try {
        const buffer = await excel.xlsx.writeBuffer();
        const uint8Array = new Uint8Array(buffer as ArrayBuffer);
        const input: PutObjectCommandInput = {
            Bucket: bucketName,
            Key: fileName,
            Body: uint8Array,
        };
        const command = new PutObjectCommand(input);
        const response = await client.send(command);
        console.log("Upload response:", response);
        return true;
    } catch (error) {
        console.error("Error uploading to S3:", error);
        return false;
    }
}

Even though the code seems correct, I still have no idea what is happening, for now, I disabled the createAt filter in the app, but hopefully this gets resolved soon!

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingp2

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions