diff --git a/.github/workflows/azure-functions-app-nodejs.yml b/.github/workflows/azure-functions-app-nodejs.yml new file mode 100644 index 0000000..b504ead --- /dev/null +++ b/.github/workflows/azure-functions-app-nodejs.yml @@ -0,0 +1,49 @@ +name: Deploy Node.js project to Azure Function App + +on: + push: + branches: ["main"] + workflow_dispatch: + +env: + AZURE_FUNCTIONAPP_NAME: ${{ vars.AZURE_FUNCTIONAPP_NAME }} + AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your function app project, defaults to the repository root + NODE_VERSION: '20.x' + +jobs: + build-and-deploy: + runs-on: windows-latest + environment: dev + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@v4 + + - name: Azure Login + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Setup Node ${{ env.NODE_VERSION }} Environment + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: 'Build project' + shell: bash + run: | + pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}' + npm install + npm run build + npm run test --if-present + npm prune --production # Remove dev dependencies + popd + + - name: 'Run Azure Functions Action' + uses: Azure/functions-action@v1 + id: fa + with: + app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }} + package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} + diff --git a/README.md b/README.md index 217afa1..4c3415e 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ The resources deployed in Azure are configured with a high level of security: + [Node.js 20](https://www.nodejs.org/) + [Azure Functions Core Tools](https://learn.microsoft.com/azure/azure-functions/functions-run-local?pivots=programming-language-typescript#install-the-azure-functions-core-tools) + [Azure Developer CLI (AZD)](https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd) -+ Be `Owner` of the subscription (or have [`Role Based Access Control Administrator`](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/privileged#role-based-access-control-administrator)), to successfully assign Azure RBAC roles to the managed identity, as part of the provisioning process ++ Have the built-in role `Owner`, or `Contributor` + [`Role Based Access Control Administrator`](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/privileged#role-based-access-control-administrator), to successfully assign roles to the managed identity, as part of the provisioning process + To use Visual Studio Code to run and debug locally: + [Visual Studio Code](https://code.visualstudio.com/) + [Azure Functions extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions) diff --git a/azure.yaml b/azure.yaml index b0e8365..f89e30f 100644 --- a/azure.yaml +++ b/azure.yaml @@ -2,7 +2,7 @@ name: functions-quickstart-spo-azd metadata: - template: Yvand/functions-quickstart-spo-azd@1.0.0.rc-7 + template: Yvand/functions-quickstart-spo-azd@1.0.0.rc-8 services: api: project: . diff --git a/infra/main.parameters.json b/infra/main.parameters.json index f773333..0549ddd 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -18,8 +18,8 @@ }, "appSettings": { "value": { - "TenantPrefix": "${TenantPrefix}", - "SiteRelativePath": "${SiteRelativePath}", + "TenantPrefix": "${APPSETTING_TenantPrefix}", + "SiteRelativePath": "${APPSETTING_SiteRelativePath}", "WebhookHistoryListTitle": "webhookHistory" } } diff --git a/package-lock.json b/package-lock.json index 9e49d94..085bc24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "functions-quickstart-spo-azd", - "version": "1.0.0.rc-7", + "version": "1.0.0.rc-8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "functions-quickstart-spo-azd", - "version": "1.0.0.rc-7", + "version": "1.0.0.rc-8", "license": "MIT", "dependencies": { "@azure/functions": "^4.6.0", diff --git a/package.json b/package.json index fe307c5..5dd53ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "functions-quickstart-spo-azd", - "version": "1.0.0.rc-7", + "version": "1.0.0.rc-8", "author": { "name": "Yvan Duhamel" }, @@ -10,7 +10,7 @@ }, "description": "This Quickstart uses Azure Developer command-line (azd) tools to create functions for SharePoint Online", "license": "MIT", - "main": "dist/src/functions-definition/*.js", + "main": "dist/src/functions/*.js", "type": "module", "scripts": { "build": "tsc", diff --git a/src/functions-definition/funcs-debug-def.ts b/src/functions-definition/funcs-debug-def.ts deleted file mode 100644 index a5ed0dc..0000000 --- a/src/functions-definition/funcs-debug-def.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { app } from "@azure/functions"; -import { getAccessToken, getWeb } from "../debug/funcs-debug-impl.js"; - -app.http('debug-getAccessToken', { methods: ['GET'], authLevel: 'admin', handler: getAccessToken, route: 'debug/getAccessToken' }); -app.http('debug-getWeb', { methods: ['GET'], authLevel: 'function', handler: getWeb, route: 'debug/getWeb' }); diff --git a/src/functions-definition/funcs-webhooks-def.ts b/src/functions-definition/funcs-webhooks-def.ts deleted file mode 100644 index 42faed1..0000000 --- a/src/functions-definition/funcs-webhooks-def.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { app } from "@azure/functions"; -import { registerWebhook, listWehhooks, wehhookService, removeWehhook, showWehhook } from "../webhooks/funcs-webhooks-impl.js" - -app.http('webhooks-register', { methods: ['POST'], authLevel: 'function', handler: registerWebhook, route: 'webhooks/register' }); -app.http('webhooks-service', { methods: ['POST'], authLevel: 'function', handler: wehhookService, route: 'webhooks/service' }); -app.http('webhooks-list', { methods: ['GET'], authLevel: 'function', handler: listWehhooks, route: 'webhooks/list' }); -app.http('webhooks-remove', { methods: ['POST'], authLevel: 'function', handler: removeWehhook, route: 'webhooks/remove' }); -app.http('webhooks-show', { methods: ['GET'], authLevel: 'function', handler: showWehhook, route: 'webhooks/show' }); diff --git a/src/debug/funcs-debug-impl.ts b/src/functions/functions-debug.ts similarity index 81% rename from src/debug/funcs-debug-impl.ts rename to src/functions/functions-debug.ts index 8fa1d68..20bd4ac 100644 --- a/src/debug/funcs-debug-impl.ts +++ b/src/functions/functions-debug.ts @@ -1,13 +1,9 @@ -import { HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; -import "@pnp/sp/items/index.js"; -import "@pnp/sp/lists/index.js"; -import "@pnp/sp/subscriptions/index.js"; -import "@pnp/sp/webs/index.js"; +import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; import { CommonConfig, safeWait } from "../utils/common.js"; import { logError } from "../utils/loggingHandler.js"; import { getSharePointSiteInfo, getSpAccessToken, getSPFI } from "../utils/spAuthentication.js"; -export async function getAccessToken(request: HttpRequest, context: InvocationContext): Promise { +async function getAccessToken(request: HttpRequest, context: InvocationContext): Promise { const tenantPrefix = request.query.get('tenantPrefix') || CommonConfig.TenantPrefix; try { const token = await getSpAccessToken(tenantPrefix); @@ -44,3 +40,6 @@ export async function getWeb(request: HttpRequest, context: InvocationContext): return { status: errorDetails.httpStatus, jsonBody: errorDetails }; } }; + +app.http('debug-getAccessToken', { methods: ['GET'], authLevel: 'admin', handler: getAccessToken, route: 'debug/getAccessToken' }); +app.http('debug-getWeb', { methods: ['GET'], authLevel: 'function', handler: getWeb, route: 'debug/getWeb' }); diff --git a/src/webhooks/funcs-webhooks-impl.ts b/src/functions/functions-webhooks.ts similarity index 73% rename from src/webhooks/funcs-webhooks-impl.ts rename to src/functions/functions-webhooks.ts index c474cb4..b67fca8 100644 --- a/src/webhooks/funcs-webhooks-impl.ts +++ b/src/functions/functions-webhooks.ts @@ -1,144 +1,151 @@ -import { HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; -import { dateAdd } from "@pnp/core"; -import "@pnp/sp/items/index.js"; -import "@pnp/sp/lists/index.js"; -import { IListEnsureResult } from "@pnp/sp/lists/types.js"; -import "@pnp/sp/subscriptions/index.js"; -import "@pnp/sp/webs/index.js"; -import { CommonConfig, ISharePointWeebhookEvent, ISubscriptionResponse, safeWait } from "../utils/common.js"; -import { logError, logInfo } from "../utils/loggingHandler.js"; -import { getSharePointSiteInfo, getSPFI } from "../utils/spAuthentication.js"; - -export async function registerWebhook(request: HttpRequest, context: InvocationContext): Promise { - try { - const tenantPrefix = request.query.get('tenantPrefix') || undefined; - const siteRelativePath = request.query.get('siteRelativePath') || undefined; - const listTitle = request.query.get('listTitle'); - const notificationUrl = request.query.get('notificationUrl'); - - if (!listTitle || !notificationUrl) { return { status: 400, body: `Required parameters are missing.` }; } - - const sharePointSite = getSharePointSiteInfo(tenantPrefix, siteRelativePath); - const sp = getSPFI(sharePointSite); - const expiryDate: Date = dateAdd(new Date(), "day", 180) as Date; // Set the expiry date to 180 days from now, which is the maximum allowed for the webhook expiry date. - let result: any, error: any; - [result, error] = await safeWait(sp.web.lists.getByTitle(listTitle).subscriptions.add(notificationUrl, expiryDate.toISOString())); - if (error) { - const errorDetails = await logError(context, error, `Could not register webhook '${notificationUrl}' in list '${listTitle}'`); - return { status: errorDetails.httpStatus, jsonBody: errorDetails }; - } - logInfo(context, `Attempted to register webhook '${notificationUrl}' to list '${listTitle}' with expiry date '${expiryDate.toISOString()}'. Result: ${JSON.stringify(result)}`); - return { status: 200, jsonBody: result }; - } - catch (error: unknown) { - const errorDetails = await logError(context, error, context.functionName); - return { status: errorDetails.httpStatus, jsonBody: errorDetails }; - } -}; - -export async function wehhookService(request: HttpRequest, context: InvocationContext): Promise { - try { - const validationtoken = request.query.get('validationtoken'); - if (validationtoken) { - logInfo(context, `Validated webhook registration with validation token: ${validationtoken}`); - return { status: 200, headers: { 'Content-Type': 'text/plain' }, body: validationtoken }; - } - - const body: ISharePointWeebhookEvent = await request.json() as ISharePointWeebhookEvent; - const message = logInfo(context, `Received webhook notification: ${body.value.length} event(s) for resource '${body.value[0].resource}' on site '${body.value[0].siteUrl}'`); - - const sharePointSite = getSharePointSiteInfo(); - const sp = getSPFI(sharePointSite); - let webhookHistoryListEnsureResult: IListEnsureResult, error: any; - [webhookHistoryListEnsureResult, error] = await safeWait(sp.web.lists.ensure(CommonConfig.WebhookHistoryListTitle)); - if (error) { - const errorDetails = await logError(context, error, `Could not ensure that list '${CommonConfig.WebhookHistoryListTitle}' exists`); - return { status: errorDetails.httpStatus }; - } - if (webhookHistoryListEnsureResult.created === true) { - logInfo(context, `List '${CommonConfig.WebhookHistoryListTitle}' (to log the webhook notifications) did not exist and was just created.`); - } - let result: any; - [result, error] = await safeWait(sp.web.lists.getByTitle(CommonConfig.WebhookHistoryListTitle).items.add({ - Title: JSON.stringify(message), - })); - if (error) { - const errorDetails = await logError(context, error, `Could not add an item to the list '${CommonConfig.WebhookHistoryListTitle}'`); - return { status: errorDetails.httpStatus }; - } - return { status: 200 }; - } - catch (error: unknown) { - const errorDetails = await logError(context, error, context.functionName); - return { status: errorDetails.httpStatus }; - } -}; - -export async function listWehhooks(request: HttpRequest, context: InvocationContext): Promise { - try { - const tenantPrefix = request.query.get('tenantPrefix') || undefined; - const siteRelativePath = request.query.get('siteRelativePath') || undefined; - const listTitle = request.query.get('listTitle'); - - if (!listTitle) { return { status: 400, body: `Required parameters are missing.` }; } - - const sharePointSite = getSharePointSiteInfo(tenantPrefix, siteRelativePath); - const sp = getSPFI(sharePointSite); - let result: any, error: any; - [result, error] = await safeWait(sp.web.lists.getByTitle(listTitle).subscriptions()); - if (error) { - const errorDetails = await logError(context, error, `Could not list webhook for web '${sharePointSite.siteRelativePath}' and list '${listTitle}'`); - return { status: errorDetails.httpStatus, jsonBody: errorDetails }; - }; - logInfo(context, `Webhooks registered on web '${sharePointSite.siteRelativePath}' and list '${listTitle}': ${JSON.stringify(result)}`); - return { status: 200, jsonBody: result }; - } - catch (error: unknown) { - const errorDetails = await logError(context, error, context.functionName); - return { status: errorDetails.httpStatus, jsonBody: errorDetails }; - } -}; - -export async function showWehhook(request: HttpRequest, context: InvocationContext): Promise { - try { - const notificationUrl = request.query.get('notificationUrl'); - if (!notificationUrl) { return { status: 400, body: `Required parameters are missing.` }; } - - const webhooksResponse = await listWehhooks(request, context); - if (!webhooksResponse || !webhooksResponse.jsonBody) { return { status: 200, jsonBody: {} }; } - if (webhooksResponse.status !== 200) { return webhooksResponse; } - const webhooks: ISubscriptionResponse[] = webhooksResponse.jsonBody; - const webhook = webhooks.find((element) => element.notificationUrl === notificationUrl); - return { status: 200, jsonBody: webhook ? webhook : {} }; - } - catch (error: unknown) { - const errorDetails = await logError(context, error, context.functionName); - return { status: errorDetails.httpStatus, jsonBody: { status: 'error', message: errorDetails } }; - } -}; - -export async function removeWehhook(request: HttpRequest, context: InvocationContext): Promise { - try { - const tenantPrefix = request.query.get('tenantPrefix') || undefined; - const siteRelativePath = request.query.get('siteRelativePath') || undefined; - const listTitle = request.query.get('listTitle'); - const webhookId = request.query.get('webhookId'); - - if (!listTitle || !webhookId) { return { status: 400, body: `Required parameters are missing.` }; } - - const sharePointSite = getSharePointSiteInfo(tenantPrefix, siteRelativePath); - const sp = getSPFI(sharePointSite); - let result: any, error: any; - [result, error] = await safeWait(sp.web.lists.getByTitle(listTitle).subscriptions.getById(webhookId).delete()); - if (error) { - const errorDetails = await logError(context, error, `Could not delete webhook '${webhookId}' for web '${sharePointSite.siteRelativePath}' and list '${listTitle}'`); - return { status: errorDetails.httpStatus, jsonBody: errorDetails }; - } - logInfo(context, `Deleted webhook '${webhookId}' registered on web '${sharePointSite.siteRelativePath}' and list '${listTitle}'.`); - return { status: 204 }; - } - catch (error: unknown) { - const errorDetails = await logError(context, error, context.functionName); - return { status: errorDetails.httpStatus, jsonBody: { status: 'error', message: errorDetails } }; - } -}; +import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; +import { dateAdd } from "@pnp/core"; +import "@pnp/sp/items/index.js"; +import "@pnp/sp/lists/index.js"; +import { IListEnsureResult } from "@pnp/sp/lists/types.js"; +import "@pnp/sp/subscriptions/index.js"; +import "@pnp/sp/webs/index.js"; +import { CommonConfig, ISharePointWeebhookEvent, ISubscriptionResponse, safeWait } from "../utils/common.js"; +import { logError, logMessage } from "../utils/loggingHandler.js"; +import { getSharePointSiteInfo, getSPFI } from "../utils/spAuthentication.js"; + +async function registerWebhook(request: HttpRequest, context: InvocationContext): Promise { + try { + const tenantPrefix = request.query.get('tenantPrefix') || undefined; + const siteRelativePath = request.query.get('siteRelativePath') || undefined; + const listTitle = request.query.get('listTitle'); + const notificationUrl = request.query.get('notificationUrl'); + + if (!listTitle || !notificationUrl) { return { status: 400, body: `Required parameters are missing.` }; } + + const sharePointSite = getSharePointSiteInfo(tenantPrefix, siteRelativePath); + const sp = getSPFI(sharePointSite); + const expiryDate: Date = dateAdd(new Date(), "day", 180) as Date; // Set the expiry date to 180 days from now, which is the maximum allowed for the webhook expiry date. + let result: any, error: any; + [result, error] = await safeWait(sp.web.lists.getByTitle(listTitle).subscriptions.add(notificationUrl, expiryDate.toISOString())); + if (error) { + const errorDetails = await logError(context, error, `Could not register webhook '${notificationUrl}' in list '${listTitle}'`); + return { status: errorDetails.httpStatus, jsonBody: errorDetails }; + } + logMessage(context, `Attempted to register webhook '${notificationUrl}' to list '${listTitle}' with expiry date '${expiryDate.toISOString()}'. Result: ${JSON.stringify(result)}`); + return { status: 200, jsonBody: result }; + } + catch (error: unknown) { + const errorDetails = await logError(context, error, context.functionName); + return { status: errorDetails.httpStatus, jsonBody: errorDetails }; + } +}; + +async function wehhookService(request: HttpRequest, context: InvocationContext): Promise { + try { + const validationtoken = request.query.get('validationtoken'); + if (validationtoken) { + logMessage(context, `Validated webhook registration with validation token: ${validationtoken}`); + return { status: 200, headers: { 'Content-Type': 'text/plain' }, body: validationtoken }; + } + + const body: ISharePointWeebhookEvent = await request.json() as ISharePointWeebhookEvent; + const message = logMessage(context, `Received webhook notification: ${body.value.length} event(s) for resource '${body.value[0].resource}' on site '${body.value[0].siteUrl}'`); + + const sharePointSite = getSharePointSiteInfo(); + const sp = getSPFI(sharePointSite); + let webhookHistoryListEnsureResult: IListEnsureResult, error: any; + [webhookHistoryListEnsureResult, error] = await safeWait(sp.web.lists.ensure(CommonConfig.WebhookHistoryListTitle)); + if (error) { + const errorDetails = await logError(context, error, `Could not ensure that list '${CommonConfig.WebhookHistoryListTitle}' exists`); + return { status: errorDetails.httpStatus }; + } + if (webhookHistoryListEnsureResult.created === true) { + logMessage(context, `List '${CommonConfig.WebhookHistoryListTitle}' (to log the webhook notifications) did not exist and was just created.`); + } + let result: any; + [result, error] = await safeWait(sp.web.lists.getByTitle(CommonConfig.WebhookHistoryListTitle).items.add({ + Title: JSON.stringify(message), + })); + if (error) { + const errorDetails = await logError(context, error, `Could not add an item to the list '${CommonConfig.WebhookHistoryListTitle}'`); + return { status: errorDetails.httpStatus }; + } + return { status: 200 }; + } + catch (error: unknown) { + const errorDetails = await logError(context, error, context.functionName); + return { status: errorDetails.httpStatus }; + } +}; + +async function listWehhooks(request: HttpRequest, context: InvocationContext): Promise { + try { + const tenantPrefix = request.query.get('tenantPrefix') || undefined; + const siteRelativePath = request.query.get('siteRelativePath') || undefined; + const listTitle = request.query.get('listTitle'); + + if (!listTitle) { return { status: 400, body: `Required parameters are missing.` }; } + + const sharePointSite = getSharePointSiteInfo(tenantPrefix, siteRelativePath); + const sp = getSPFI(sharePointSite); + let result: any, error: any; + [result, error] = await safeWait(sp.web.lists.getByTitle(listTitle).subscriptions()); + if (error) { + const errorDetails = await logError(context, error, `Could not list webhook for web '${sharePointSite.siteRelativePath}' and list '${listTitle}'`); + return { status: errorDetails.httpStatus, jsonBody: errorDetails }; + }; + logMessage(context, `Webhooks registered on web '${sharePointSite.siteRelativePath}' and list '${listTitle}': ${JSON.stringify(result)}`); + return { status: 200, jsonBody: result }; + } + catch (error: unknown) { + const errorDetails = await logError(context, error, context.functionName); + return { status: errorDetails.httpStatus, jsonBody: errorDetails }; + } +}; + +async function showWehhook(request: HttpRequest, context: InvocationContext): Promise { + try { + const notificationUrl = request.query.get('notificationUrl'); + if (!notificationUrl) { return { status: 400, body: `Required parameters are missing.` }; } + + const webhooksResponse = await listWehhooks(request, context); + if (!webhooksResponse || !webhooksResponse.jsonBody) { return { status: 200, jsonBody: {} }; } + if (webhooksResponse.status !== 200) { return webhooksResponse; } + const webhooks: ISubscriptionResponse[] = webhooksResponse.jsonBody; + const webhook = webhooks.find((element) => element.notificationUrl === notificationUrl); + return { status: 200, jsonBody: webhook ? webhook : {} }; + } + catch (error: unknown) { + const errorDetails = await logError(context, error, context.functionName); + return { status: errorDetails.httpStatus, jsonBody: { status: 'error', message: errorDetails } }; + } +}; + +async function removeWehhook(request: HttpRequest, context: InvocationContext): Promise { + try { + const tenantPrefix = request.query.get('tenantPrefix') || undefined; + const siteRelativePath = request.query.get('siteRelativePath') || undefined; + const listTitle = request.query.get('listTitle'); + const webhookId = request.query.get('webhookId'); + + if (!listTitle || !webhookId) { return { status: 400, body: `Required parameters are missing.` }; } + + const sharePointSite = getSharePointSiteInfo(tenantPrefix, siteRelativePath); + const sp = getSPFI(sharePointSite); + let result: any, error: any; + [result, error] = await safeWait(sp.web.lists.getByTitle(listTitle).subscriptions.getById(webhookId).delete()); + if (error) { + const errorDetails = await logError(context, error, `Could not delete webhook '${webhookId}' for web '${sharePointSite.siteRelativePath}' and list '${listTitle}'`); + return { status: errorDetails.httpStatus, jsonBody: errorDetails }; + } + logMessage(context, `Deleted webhook '${webhookId}' registered on web '${sharePointSite.siteRelativePath}' and list '${listTitle}'.`); + return { status: 204 }; + } + catch (error: unknown) { + const errorDetails = await logError(context, error, context.functionName); + return { status: errorDetails.httpStatus, jsonBody: { status: 'error', message: errorDetails } }; + } +}; + + +app.http('webhooks-register', { methods: ['POST'], authLevel: 'function', handler: registerWebhook, route: 'webhooks/register' }); +app.http('webhooks-service', { methods: ['POST'], authLevel: 'function', handler: wehhookService, route: 'webhooks/service' }); +app.http('webhooks-list', { methods: ['GET'], authLevel: 'function', handler: listWehhooks, route: 'webhooks/list' }); +app.http('webhooks-remove', { methods: ['POST'], authLevel: 'function', handler: removeWehhook, route: 'webhooks/remove' }); +app.http('webhooks-show', { methods: ['GET'], authLevel: 'function', handler: showWehhook, route: 'webhooks/show' }); diff --git a/src/webhooks/tests/azure.env.example b/src/functions/tests/azure.env.example similarity index 100% rename from src/webhooks/tests/azure.env.example rename to src/functions/tests/azure.env.example diff --git a/src/webhooks/tests/local.env.example b/src/functions/tests/local.env.example similarity index 100% rename from src/webhooks/tests/local.env.example rename to src/functions/tests/local.env.example diff --git a/src/webhooks/tests/test-functions-webhooks.http b/src/functions/tests/test-functions-webhooks.http similarity index 100% rename from src/webhooks/tests/test-functions-webhooks.http rename to src/functions/tests/test-functions-webhooks.http diff --git a/src/utils/loggingHandler.ts b/src/utils/loggingHandler.ts index 6bd9881..6b41942 100644 --- a/src/utils/loggingHandler.ts +++ b/src/utils/loggingHandler.ts @@ -86,6 +86,12 @@ export async function logError(logcontext: InvocationContext, error: Error | Htt const spCorrelationId = (error as HttpRequestError).response.headers.get("sprequestguid"); errorDocument.sprequestguid = spCorrelationId || ""; + } else if (error instanceof AggregateError) { + errorDocument.type = error.name; + errorDetails += `AggregateError with ${error.errors.length} errors: `; + for (let i = 0; i < error.errors.length; i++) { + errorDetails += `Error ${i}: ${error.errors[i].name}: ${error.errors[i].message}. `; + } } else { errorDocument.type = error.name; errorDetails += error.message; @@ -98,7 +104,7 @@ export async function logError(logcontext: InvocationContext, error: Error | Htt errorDocument.type = "unknown"; errorDetails = JSON.stringify(error); } - + errorDocument.error = errorDetails; Logger.log({ data: logcontext, @@ -115,7 +121,7 @@ export async function logError(logcontext: InvocationContext, error: Error | Htt * @param level * @returns */ -export function logInfo(logcontext: InvocationContext, message: string, level: LogLevel = LogLevel.Info): IMessageDocument { +export function logMessage(logcontext: InvocationContext, message: string, level: LogLevel = LogLevel.Info): IMessageDocument { const messageResponse: IMessageDocument = { timestamp: new Date().toISOString(), level: level, message: message }; Logger.log({ data: logcontext,