diff --git a/.config/samples-config-v3.json b/.config/samples-config-v3.json index fb8327c1d..f86307601 100644 --- a/.config/samples-config-v3.json +++ b/.config/samples-config-v3.json @@ -391,17 +391,18 @@ "suggested": false }, { - "id": "hello-world-teams-tab-and-outlook-add-in", - "shortId": "helloworld-outlook", + "id": "hello-world-teams-tab-and-office-add-in", + "shortId": "helloworld-office", "onboardDate": "2023-03-02", - "title": "Hello World Teams Tab and Outlook add-in", - "shortDescription": "A hello world project that contains both Teams Tab and Outlook add-in capability", - "fullDescription": "Microsoft Teams supports the ability to run web-based UI inside \"custom tabs\" that users can install either for just themselves (personal tabs) or within a team or group chat context. Outlook add-ins are integrations built by third parties into Outlook by using our web-based platform. Now you have the ability to create a single unit of distribution for all your Microsoft 365 extensions by using the same manifest format and schema, based on the current JSON-formatted Teams manifest.", + "title": "Hello World Teams Tab and Office add-in", + "shortDescription": "A hello world project that contains both Teams Tab and Office add-in capability", + "fullDescription": "Microsoft Teams supports the ability to run web-based UI inside \"custom tabs\" that users can install either for just themselves (personal tabs) or within a team or group chat context. Office add-ins are integrations built by third parties into Office by using our web-based platform. Now you have the ability to create a single unit of distribution for all your Microsoft 365 extensions by using the same manifest format and schema, based on the current JSON-formatted Teams manifest.", "types": [ "Tab" ], "tags": [ "Tab", + "Office Add-in", "Outlook Add-in" ], "time": "5min to run", diff --git a/bot-sso-docker/README.md b/bot-sso-docker/README.md index 11acb55b8..4a81f9d62 100644 --- a/bot-sso-docker/README.md +++ b/bot-sso-docker/README.md @@ -47,7 +47,7 @@ This sample demonstrate how to containerize a Teams App and integrate the Docker - **Provision Infrastructure**: Automate the provisioning of Azure Container Apps and Azure Container Registry using Bicep templates. Refer to this [Azure Bicep example](./infra/azure.bicep). -- **Build and Deploy to ACA**: Build the Docker image and push it to Azure Container Registry using Docker CLI. Deploy the image to Azure Container Apps using Azure CLI. Refer to the deployment scripts in this [teamsapp.yml example](./teamsapp.yml). +- **Build and Deploy to ACA**: Using Azure CLI, build and push the Docker image to Azure Container Registry, then deploy it to Azure Container Apps. Refer to the deployment scripts in this [teamsapp.yml example](./teamsapp.yml). ## Minimal path to awesome diff --git a/bot-sso-docker/teamsapp.yml b/bot-sso-docker/teamsapp.yml index b50a22cad..dd981eba3 100644 --- a/bot-sso-docker/teamsapp.yml +++ b/bot-sso-docker/teamsapp.yml @@ -77,20 +77,15 @@ deploy: run: echo "::set-teamsfx-env AZURE_CONTAINER_IMAGE=${{AZURE_CONTAINER_REGISTRY_SERVER}}/sso-bot" - - uses: script - name: build container image - with: - run: docker build -t ${{AZURE_CONTAINER_IMAGE}} . - - uses: script name: login azure container registry with: run: az acr login -n ${{AZURE_CONTAINER_REGISTRY_NAME}} - - uses: script - name: push container image - with: - run: docker push ${{AZURE_CONTAINER_IMAGE}} + - uses: script + name: build container image + with: + run: az acr build --registry ${{AZURE_CONTAINER_REGISTRY_NAME}} --image ${{AZURE_CONTAINER_IMAGE}} . - uses: script name: deploy container image diff --git a/hello-world-bot-with-tab/tab/vite.config.ts b/hello-world-bot-with-tab/tab/vite.config.ts index 75086e298..f59da272b 100644 --- a/hello-world-bot-with-tab/tab/vite.config.ts +++ b/hello-world-bot-with-tab/tab/vite.config.ts @@ -4,6 +4,9 @@ import fs from "fs"; export default defineConfig({ plugins: [react()], + define: { + global: 'globalThis', + }, server: { port: 53000, https: { diff --git a/hello-world-tab-docker/README.md b/hello-world-tab-docker/README.md index d080cddb5..dfa7ac9ff 100644 --- a/hello-world-tab-docker/README.md +++ b/hello-world-tab-docker/README.md @@ -57,7 +57,7 @@ This sample demonstrate how to containerize a Teams App and integrate the Docker - **Provision Infrastructure**: Automate the provisioning of Azure Container Apps and Azure Container Registry using Bicep templates. Refer to this [Azure Bicep example](./infra/azure.bicep). -- **Build and Deploy to ACA**: Build the Docker image and push it to Azure Container Registry using Docker CLI. Deploy the image to Azure Container Apps using Azure CLI. Refer to the deployment scripts in this [teamsapp.yml example](./teamsapp.yml). +- **Build and Deploy to ACA**: Using Azure CLI, build and push the Docker image to Azure Container Registry, then deploy it to Azure Container Apps. Refer to the deployment scripts in this [teamsapp.yml example](./teamsapp.yml). ## Minimal path to awesome diff --git a/hello-world-teams-tab-and-outlook-add-in/.gitignore b/hello-world-teams-tab-and-office-add-in/.gitignore similarity index 92% rename from hello-world-teams-tab-and-outlook-add-in/.gitignore rename to hello-world-teams-tab-and-office-add-in/.gitignore index 75b59b711..18e94fecc 100644 --- a/hello-world-teams-tab-and-outlook-add-in/.gitignore +++ b/hello-world-teams-tab-and-office-add-in/.gitignore @@ -1,6 +1,5 @@ # TeamsFx files env/.env.*.user -env/.env.local .DS_Store .localConfigs diff --git a/hello-world-teams-tab-and-outlook-add-in/.vscode/extensions.json b/hello-world-teams-tab-and-office-add-in/.vscode/extensions.json similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/.vscode/extensions.json rename to hello-world-teams-tab-and-office-add-in/.vscode/extensions.json diff --git a/hello-world-teams-tab-and-outlook-add-in/.vscode/launch.json b/hello-world-teams-tab-and-office-add-in/.vscode/launch.json similarity index 70% rename from hello-world-teams-tab-and-outlook-add-in/.vscode/launch.json rename to hello-world-teams-tab-and-office-add-in/.vscode/launch.json index d39085f8b..98a7868fc 100644 --- a/hello-world-teams-tab-and-outlook-add-in/.vscode/launch.json +++ b/hello-world-teams-tab-and-office-add-in/.vscode/launch.json @@ -1,6 +1,36 @@ { "version": "0.2.0", "configurations": [ + { + "name": "Debug in Word Desktop (Edge Chromium)", + "type": "msedge", + "request": "attach", + "port": 9229, + "timeout": 600000, + "webRoot": "${workspaceRoot}/add-in/", + "preLaunchTask": "Start Word Add-in Locally", + "postDebugTask": "Stop Debug" + }, + { + "name": "Debug in Excel Desktop (Edge Chromium)", + "type": "msedge", + "request": "attach", + "port": 9229, + "timeout": 600000, + "webRoot": "${workspaceRoot}/add-in/", + "preLaunchTask": "Start Excel Add-in Locally", + "postDebugTask": "Stop Debug" + }, + { + "name": "Debug in PowerPoint Desktop (Edge Chromium)", + "type": "msedge", + "request": "attach", + "port": 9229, + "timeout": 600000, + "webRoot": "${workspaceRoot}/add-in/", + "preLaunchTask": "Start PowerPoint Add-in Locally", + "postDebugTask": "Stop Debug" + }, { "name": "Debug in Outlook Desktop (Edge Chromium)", "type": "msedge", @@ -8,7 +38,7 @@ "port": 9229, "timeout": 600000, "webRoot": "${workspaceRoot}/add-in/", - "preLaunchTask": "Start Add-in Locally", + "preLaunchTask": "Start Outlook Add-in Locally", "postDebugTask": "Stop Debug" }, { diff --git a/hello-world-teams-tab-and-outlook-add-in/.vscode/settings.json b/hello-world-teams-tab-and-office-add-in/.vscode/settings.json similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/.vscode/settings.json rename to hello-world-teams-tab-and-office-add-in/.vscode/settings.json diff --git a/hello-world-teams-tab-and-outlook-add-in/.vscode/tasks.json b/hello-world-teams-tab-and-office-add-in/.vscode/tasks.json similarity index 74% rename from hello-world-teams-tab-and-outlook-add-in/.vscode/tasks.json rename to hello-world-teams-tab-and-office-add-in/.vscode/tasks.json index dffe951cd..8737a230d 100644 --- a/hello-world-teams-tab-and-outlook-add-in/.vscode/tasks.json +++ b/hello-world-teams-tab-and-office-add-in/.vscode/tasks.json @@ -116,21 +116,69 @@ "showReuseMessage": false } }, + { + "label": "Debug: Excel Desktop", + "type": "npm", + "script": "start:desktop:excel", + "options": { + "cwd": "${workspaceFolder}/add-in/" + }, + "presentation": { + "clear": true, + "panel": "dedicated" + }, + "problemMatcher": [], + "dependsOn": [ + "Install" + ] + }, { "label": "Debug: Outlook Desktop", "type": "npm", - "script": "start:desktop -- --app outlook", + "script": "start:desktop:outlook", "options": { "cwd": "${workspaceFolder}/add-in/" }, + "presentation": { + "clear": true, + "panel": "dedicated" + }, + "problemMatcher": [], "dependsOn": [ "Install" - ], + ] + }, + { + "label": "Debug: PowerPoint Desktop", + "type": "npm", + "script": "start:desktop:powerpoint", + "options": { + "cwd": "${workspaceFolder}/add-in/" + }, "presentation": { "clear": true, - "panel": "dedicated", + "panel": "dedicated" }, - "problemMatcher": [] + "problemMatcher": [], + "dependsOn": [ + "Install" + ] + }, + { + "label": "Debug: Word Desktop", + "type": "npm", + "script": "start:desktop:word", + "options": { + "cwd": "${workspaceFolder}/add-in/" + }, + "presentation": { + "clear": true, + "panel": "dedicated" + }, + "problemMatcher": [], + "dependsOn": [ + "Install" + ] }, { "label": "Dev Server", @@ -209,7 +257,31 @@ "problemMatcher": [] }, { - "label": "Start Add-in Locally", + "label": "Start Word Add-in Locally", + "dependsOn": [ + "Create resources", + "Debug: Word Desktop" + ], + "dependsOrder": "sequence" + }, + { + "label": "Start Excel Add-in Locally", + "dependsOn": [ + "Create resources", + "Debug: Excel Desktop" + ], + "dependsOrder": "sequence" + }, + { + "label": "Start PowerPoint Add-in Locally", + "dependsOn": [ + "Create resources", + "Debug: PowerPoint Desktop" + ], + "dependsOrder": "sequence" + }, + { + "label": "Start Outlook Add-in Locally", "dependsOn": [ "Create resources", "Debug: Outlook Desktop" diff --git a/hello-world-teams-tab-and-outlook-add-in/README.md b/hello-world-teams-tab-and-office-add-in/README.md similarity index 90% rename from hello-world-teams-tab-and-outlook-add-in/README.md rename to hello-world-teams-tab-and-office-add-in/README.md index 6e5969060..37c455b3a 100644 --- a/hello-world-teams-tab-and-outlook-add-in/README.md +++ b/hello-world-teams-tab-and-office-add-in/README.md @@ -5,28 +5,28 @@ languages: products: - office-teams - office -name: Hello World Teams Tab and Outlook add-in -urlFragment: officedev-teamsfx-samples-tab-hello-world-teams-tab-and-outlook-add-in -description: A hello world project that contains both Teams Tab and Outlook add-in capability. +name: Hello World Teams Tab and Office add-in +urlFragment: officedev-teamsfx-samples-tab-hello-world-teams-tab-and-office-add-in +description: A hello world project that contains both Teams Tab and Office add-in capability. extensions: createdDate: "2023-03-02" --- -# Getting Started with Hello World Teams Tab and Outlook add-in Sample +# Getting Started with Hello World Teams Tab and Office add-in Sample Microsoft Teams supports the ability to run web-based UI inside "custom tabs" that users can install either for just themselves (personal tabs) or within a team or group chat context. -Outlook add-ins are integrations built by third parties into Outlook by using our web-based platform. +Office add-ins are integrations built by third parties into Office by using our web-based platform. Now you have the ability to create a single unit of distribution for all your Microsoft 365 extensions by using the same manifest format and schema, based on the current JSON-formatted Teams manifest. ## This sample illustrates -- How a Teams Tab and an Outlook add-in share the same JSON manifest in one project. +- How a Teams Tab and an Office add-in share the same JSON manifest in one project. ## Prerequisites to use this sample -- [Node.js](https://nodejs.org/), supported versions: 16, 18 -- Edge or Chrome installed for debugging Teams Tab. Edge installed for debugging Outlook add-in. +- [Node.js](https://nodejs.org/), supported versions: 18, 20 +- Edge or Chrome installed for debugging Teams Tab. Edge installed for debugging Office add-in. - Outlook for Windows: Beta Channel, Build 16320 or higher. Follow [this link](https://github.com/OfficeDev/TeamsFx/wiki/How-to-switch-Outlook-client-update-channel-and-verify-Outlook-client-build-version) for information about how to update channels and check your Outlook client build version. - An M365 account. If you do not have M365 account, apply one from [M365 developer program](https://developer.microsoft.com/en-us/microsoft-365/dev-program) - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teams-toolkit-cli) @@ -43,12 +43,12 @@ Now you have the ability to create a single unit of distribution for all your Mi After installing the app in Teams when prompted, it will launch and you'll be able to view the tab app. ![Sample tab app running in Teams](./images/teams-tab-app.PNG) -### Debug Outlook add-in +### Debug Office add-in -- Please note that the same M365 account should be used both in Teams Toolkit and Outlook. +- Please note that the same M365 account should be used both in Teams Toolkit and Office Applications (Word, Exce, Powerpoint, Outlook). - From Visual Studio Code only: use the `Run and Debug Activity Panel` in Visual Studio Code, select `Debug in Outlook Desktop (Edge Chromium)`, and click the `Run and Debug` green arrow button. Please run VSCode as administrator if localhost loopback for Microsoft Edge Webview hasn't been enabled. Once enbaled, administrator priviledge is no longer required. - ![Visual Studio Code debug configuration for Outlook](./images/outlook-debug.PNG) + ![Visual Studio Code debug configuration for Office](./images/outlook-debug.PNG) Once the Outlook app is open, select a mailbox item, and you can then use the Outlook add-in. For example, you can select the option to show a task pane. ![Outlook add-in show taskpane](./images/outlook-addin-open.PNG) @@ -56,6 +56,8 @@ Now you have the ability to create a single unit of distribution for all your Mi The taskpane should look as shown in the following image. ![Outook add-in task pane opened](./images/outlook-addin-taskpane.PNG) + Other Office Applications share the same behavior. + ### Edit the manifest You can find the app manifest in `./appPackage` folder. The folder contains one manifest file: @@ -86,9 +88,9 @@ Once the provisioning and deployment steps are finished, you can preview your Te - From Teams Toolkit CLI: execute `teamsapp preview --env dev` in your project directory to launch your application. -### Preview Outlook add-in +### Preview Office add-in -Once the provisioning and deployment steps are finished, you can preview your Outlook add-in from Visual Studio Code: +Once the provisioning and deployment steps are finished, you can preview your Office add-in from Visual Studio Code: 1. Copy the production URL from the `TAB_ENDPOINT` in env/.env.dev file. 2. Edit webpack.config.js file and change urlProd to the value you just copied. Please note to add a '/' at the end of the URL. @@ -116,6 +118,7 @@ To check that your manifest file is valid: |April 11, 2023 | yufuwang | comment out manifest validation | |May 24, 2023 | yefuwang | update outdated content | |September 10, 2023 | joshuapa | added images | +|Nov 29, 2024 | hermanwen | replace outlook with office capability | ## Feedback diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/.eslintrc.json b/hello-world-teams-tab-and-office-add-in/add-in/.eslintrc.json similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/add-in/.eslintrc.json rename to hello-world-teams-tab-and-office-add-in/add-in/.eslintrc.json diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/assets/icon-128.png b/hello-world-teams-tab-and-office-add-in/add-in/assets/icon-128.png similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/add-in/assets/icon-128.png rename to hello-world-teams-tab-and-office-add-in/add-in/assets/icon-128.png diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/assets/icon-16.png b/hello-world-teams-tab-and-office-add-in/add-in/assets/icon-16.png similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/add-in/assets/icon-16.png rename to hello-world-teams-tab-and-office-add-in/add-in/assets/icon-16.png diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/assets/icon-32.png b/hello-world-teams-tab-and-office-add-in/add-in/assets/icon-32.png similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/add-in/assets/icon-32.png rename to hello-world-teams-tab-and-office-add-in/add-in/assets/icon-32.png diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/assets/icon-64.png b/hello-world-teams-tab-and-office-add-in/add-in/assets/icon-64.png similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/add-in/assets/icon-64.png rename to hello-world-teams-tab-and-office-add-in/add-in/assets/icon-64.png diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/assets/icon-80.png b/hello-world-teams-tab-and-office-add-in/add-in/assets/icon-80.png similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/add-in/assets/icon-80.png rename to hello-world-teams-tab-and-office-add-in/add-in/assets/icon-80.png diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/assets/logo-filled.png b/hello-world-teams-tab-and-office-add-in/add-in/assets/logo-filled.png similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/add-in/assets/logo-filled.png rename to hello-world-teams-tab-and-office-add-in/add-in/assets/logo-filled.png diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/babel.config.json b/hello-world-teams-tab-and-office-add-in/add-in/babel.config.json similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/add-in/babel.config.json rename to hello-world-teams-tab-and-office-add-in/add-in/babel.config.json diff --git a/hello-world-teams-tab-and-office-add-in/add-in/package.json b/hello-world-teams-tab-and-office-add-in/add-in/package.json new file mode 100644 index 000000000..18d7013a1 --- /dev/null +++ b/hello-world-teams-tab-and-office-add-in/add-in/package.json @@ -0,0 +1,69 @@ +{ + "name": "office-addin-taskpane", + "version": "0.0.1", + "repository": { + "type": "git", + "url": "https://github.com/OfficeDev/Office-Addin-TaskPane.git" + }, + "license": "MIT", + "config": { + "app_to_debug": "excel", + "app_type_to_debug": "desktop", + "dev_server_port": 53000 + }, + "scripts": { + "build": "webpack --mode production", + "build:dev": "webpack --mode development", + "dev-server": "webpack serve --mode development", + "lint": "office-addin-lint check", + "lint:fix": "office-addin-lint fix", + "prettier": "office-addin-lint prettier", + "signin": "office-addin-dev-settings m365-account login", + "signout": "office-addin-dev-settings m365-account logout", + "start": "office-addin-debugging start ../appPackage/build/appPackage.local.zip", + "start:desktop": "office-addin-debugging start ../appPackage/build/appPackage.local.zip desktop", + "start:desktop:word": "office-addin-debugging start ../appPackage/build/appPackage.local.zip --app word", + "start:desktop:excel": "office-addin-debugging start ../appPackage/build/appPackage.local.zip --app excel", + "start:desktop:powerpoint": "office-addin-debugging start ../appPackage/build/appPackage.local.zip --app powerpoint", + "start:desktop:outlook": "office-addin-debugging start ../appPackage/build/appPackage.local.zip --app outlook", + "start:web": "office-addin-debugging start ../appPackage/build/appPackage.local.zip web", + "stop": "office-addin-debugging stop ../appPackage/build/manifest.local.json", + "validate": "office-addin-manifest validate ../appPackage/build/manifest.local.json", + "watch": "webpack --mode development --watch" + }, + "dependencies": { + "core-js": "^3.36.0", + "regenerator-runtime": "^0.14.1" + }, + "devDependencies": { + "@babel/core": "^7.24.0", + "@babel/preset-typescript": "^7.23.3", + "@types/office-js": "^1.0.377", + "@types/office-runtime": "^1.0.35", + "babel-loader": "^9.1.3", + "copy-webpack-plugin": "^12.0.2", + "eslint-plugin-office-addins": "^3.0.2", + "file-loader": "^6.2.0", + "html-loader": "^5.0.0", + "html-webpack-plugin": "^5.6.0", + "office-addin-cli": "^1.6.3", + "office-addin-debugging": "^5.1.4", + "office-addin-dev-certs": "^1.13.3", + "office-addin-lint": "^2.3.3", + "office-addin-manifest": "^1.13.4", + "office-addin-prettier-config": "^1.2.1", + "os-browserify": "^0.3.0", + "process": "^0.11.10", + "source-map-loader": "^5.0.0", + "ts-loader": "^9.5.1", + "typescript": "^5.4.2", + "webpack": "^5.90.3", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "5.0.3" + }, + "prettier": "office-addin-prettier-config", + "browserslist": [ + "last 2 versions", + "ie 11" + ] +} \ No newline at end of file diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/src/commands/commands.html b/hello-world-teams-tab-and-office-add-in/add-in/src/commands/commands.html similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/add-in/src/commands/commands.html rename to hello-world-teams-tab-and-office-add-in/add-in/src/commands/commands.html diff --git a/hello-world-teams-tab-and-office-add-in/add-in/src/commands/commands.ts b/hello-world-teams-tab-and-office-add-in/add-in/src/commands/commands.ts new file mode 100644 index 000000000..5845a7156 --- /dev/null +++ b/hello-world-teams-tab-and-office-add-in/add-in/src/commands/commands.ts @@ -0,0 +1,27 @@ +import { insertBlueParagraphInWord } from "./word"; +import { setRangeColorInExcel } from "./excel"; +import { insertTextInPowerPoint } from "./powerpoint"; +import { setNotificationInOutlook } from "./outlook"; + +/* global Office */ + +// Register the add-in commands with the Office host application. +Office.onReady(async (info) => { + switch (info.host) { + case Office.HostType.Word: + Office.actions.associate("action", insertBlueParagraphInWord); + break; + case Office.HostType.Excel: + Office.actions.associate("action", setRangeColorInExcel); + break; + case Office.HostType.PowerPoint: + Office.actions.associate("action", insertTextInPowerPoint); + break; + case Office.HostType.Outlook: + Office.actions.associate("action", setNotificationInOutlook); + break; + default: { + throw new Error(`${info.host} not supported.`); + } + } +}); diff --git a/hello-world-teams-tab-and-office-add-in/add-in/src/commands/excel.ts b/hello-world-teams-tab-and-office-add-in/add-in/src/commands/excel.ts new file mode 100644 index 000000000..a8fcd2281 --- /dev/null +++ b/hello-world-teams-tab-and-office-add-in/add-in/src/commands/excel.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. + * See LICENSE in the project root for license information. + */ + +/* global Office Excel console */ + +/** + * Set range color to selected range in excel when the add-in command is executed. + * @param event + */ +export async function setRangeColorInExcel(event: Office.AddinCommands.Event) { + try { + await Excel.run(async (context) => { + const range = context.workbook.getSelectedRange(); + range.format.fill.color = "yellow"; + await context.sync(); + }); + } catch (error) { + // Note: In a production add-in, notify the user through your add-in's UI. + console.error(error); + } + + // Be sure to indicate when the add-in command function is complete + event.completed(); +} diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/src/commands/commands.ts b/hello-world-teams-tab-and-office-add-in/add-in/src/commands/outlook.ts similarity index 50% rename from hello-world-teams-tab-and-outlook-add-in/add-in/src/commands/commands.ts rename to hello-world-teams-tab-and-office-add-in/add-in/src/commands/outlook.ts index 7149de7c7..9d65fd949 100644 --- a/hello-world-teams-tab-and-outlook-add-in/add-in/src/commands/commands.ts +++ b/hello-world-teams-tab-and-office-add-in/add-in/src/commands/outlook.ts @@ -3,17 +3,13 @@ * See LICENSE in the project root for license information. */ -/* global global, Office, self, window */ - -Office.onReady(() => { - // If needed, Office.js is ready to be called -}); +/* global Office */ /** - * Shows a notification when the add-in command is executed. + * Show an outlook notification when the add-in command is executed. * @param event */ -function action(event: Office.AddinCommands.Event) { +export function setNotificationInOutlook(event: Office.AddinCommands.Event) { const message: Office.NotificationMessageDetails = { type: Office.MailboxEnums.ItemNotificationMessageType.InformationalMessage, message: "Performed action.", @@ -21,24 +17,9 @@ function action(event: Office.AddinCommands.Event) { persistent: true, }; - // Show a notification message + // Show a notification message. Office.context.mailbox.item.notificationMessages.replaceAsync("ActionPerformanceNotification", message); - // Be sure to indicate when the add-in command function is complete + // Be sure to indicate when the add-in command function is complete. event.completed(); } - -function getGlobal() { - return typeof self !== "undefined" - ? self - : typeof window !== "undefined" - ? window - : typeof global !== "undefined" - ? global - : undefined; -} - -const g = getGlobal() as any; - -// The add-in command functions need to be available in global scope -g.action = action; diff --git a/hello-world-teams-tab-and-office-add-in/add-in/src/commands/powerpoint.ts b/hello-world-teams-tab-and-office-add-in/add-in/src/commands/powerpoint.ts new file mode 100644 index 000000000..df08cf003 --- /dev/null +++ b/hello-world-teams-tab-and-office-add-in/add-in/src/commands/powerpoint.ts @@ -0,0 +1,29 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. + * See LICENSE in the project root for license information. + */ + +/* global Office PowerPoint console */ + +/** + * Shows a notification when the add-in command is executed. + * @param event + */ +export async function insertTextInPowerPoint(event: Office.AddinCommands.Event) { + try { + await PowerPoint.run(async (context) => { + const slide = context.presentation.getSelectedSlides().getItemAt(0); + const textBox = slide.shapes.addTextBox("Hello World"); + textBox.fill.setSolidColor("white"); + textBox.lineFormat.color = "black"; + textBox.lineFormat.weight = 1; + textBox.lineFormat.dashStyle = PowerPoint.ShapeLineDashStyle.solid; + await context.sync(); + }); + } catch (error) { + console.log("Error: " + error); + } + + // Be sure to indicate when the add-in command function is complete + event.completed(); +} diff --git a/hello-world-teams-tab-and-office-add-in/add-in/src/commands/word.ts b/hello-world-teams-tab-and-office-add-in/add-in/src/commands/word.ts new file mode 100644 index 000000000..a1504ff60 --- /dev/null +++ b/hello-world-teams-tab-and-office-add-in/add-in/src/commands/word.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. + * See LICENSE in the project root for license information. + */ + +/* global Office Word console */ + +/** + * Insert a blue paragraph in word when the add-in command is executed. + * @param event + */ +export async function insertBlueParagraphInWord(event: Office.AddinCommands.Event) { + try { + await Word.run(async (context) => { + const paragraph = context.document.body.insertParagraph("Hello World", Word.InsertLocation.end); + paragraph.font.color = "blue"; + await context.sync(); + }); + } catch (error) { + // Note: In a production add-in, notify the user through your add-in's UI. + console.error(error); + } + + // Be sure to indicate when the add-in command function is complete + event.completed(); +} diff --git a/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/excel.ts b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/excel.ts new file mode 100644 index 000000000..83c4ee627 --- /dev/null +++ b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/excel.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. + * See LICENSE in the project root for license information. + */ + +/* global console, document, Excel, Office */ + +Office.onReady((info) => { + if (info.host === Office.HostType.Excel) { + document.getElementById("sideload-msg").style.display = "none"; + document.getElementById("app-body").style.display = "flex"; + document.getElementById("run").onclick = runExcel; + } +}); + +export async function runExcel() { + try { + await Excel.run(async (context) => { + /** + * Insert your Excel code here + */ + const range = context.workbook.getSelectedRange(); + + // Read the range address + range.load("address"); + + // Update the fill color + range.format.fill.color = "yellow"; + + await context.sync(); + console.log(`The range address was ${range.address}.`); + }); + } catch (error) { + console.error(error); + } +} diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/src/taskpane/taskpane.ts b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/outlook.ts similarity index 52% rename from hello-world-teams-tab-and-outlook-add-in/add-in/src/taskpane/taskpane.ts rename to hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/outlook.ts index f6d014aab..cb0c3d699 100644 --- a/hello-world-teams-tab-and-outlook-add-in/add-in/src/taskpane/taskpane.ts +++ b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/outlook.ts @@ -9,15 +9,20 @@ Office.onReady((info) => { if (info.host === Office.HostType.Outlook) { document.getElementById("sideload-msg").style.display = "none"; document.getElementById("app-body").style.display = "flex"; - document.getElementById("run").onclick = run; + document.getElementById("run").onclick = runOutlook; } }); -export async function run() { +export async function runOutlook() { /** * Insert your Outlook code here */ const item = Office.context.mailbox.item; - document.getElementById("item-subject").innerHTML = "Subject:
" + item.subject; + let insertAt = document.getElementById("item-subject"); + let label = document.createElement("b").appendChild(document.createTextNode("Subject: ")); + insertAt.appendChild(label); + insertAt.appendChild(document.createElement("br")); + insertAt.appendChild(document.createTextNode(item.subject)); + insertAt.appendChild(document.createElement("br")); } diff --git a/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/powerpoint.ts b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/powerpoint.ts new file mode 100644 index 000000000..84185b978 --- /dev/null +++ b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/powerpoint.ts @@ -0,0 +1,24 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. + * See LICENSE in the project root for license information. + */ + +/* global document, Office */ + +Office.onReady((info) => { + if (info.host === Office.HostType.PowerPoint) { + document.getElementById("sideload-msg").style.display = "none"; + document.getElementById("app-body").style.display = "flex"; + document.getElementById("run").onclick = runPowerPoint; + } +}); + +export async function runPowerPoint() { + /** + * Insert your PowerPoint code here + */ + const options: Office.SetSelectedDataOptions = { coercionType: Office.CoercionType.Text }; + + await Office.context.document.setSelectedDataAsync(" ", options); + await Office.context.document.setSelectedDataAsync("Hello World!", options); +} diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/src/taskpane/taskpane.css b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/taskpane.css similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/add-in/src/taskpane/taskpane.css rename to hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/taskpane.css diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/src/taskpane/taskpane.html b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/taskpane.html similarity index 91% rename from hello-world-teams-tab-and-outlook-add-in/add-in/src/taskpane/taskpane.html rename to hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/taskpane.html index 9e5fb7010..54fbf7efa 100644 --- a/hello-world-teams-tab-and-outlook-add-in/add-in/src/taskpane/taskpane.html +++ b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/taskpane.html @@ -26,7 +26,7 @@

Welcome

-

Please sideload your add-in to see app body.

+

Please sideload your add-in to see app body.

Discover what Office Add-ins can do for you today!

diff --git a/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/taskpane.ts b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/taskpane.ts new file mode 100644 index 000000000..359837742 --- /dev/null +++ b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/taskpane.ts @@ -0,0 +1,4 @@ +import "./excel"; +import "./outlook"; +import "./powerpoint"; +import "./word"; diff --git a/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/word.ts b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/word.ts new file mode 100644 index 000000000..ca5be3221 --- /dev/null +++ b/hello-world-teams-tab-and-office-add-in/add-in/src/taskpane/word.ts @@ -0,0 +1,30 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. + * See LICENSE in the project root for license information. + */ + +/* global document, Office, Word */ + +Office.onReady((info) => { + if (info.host === Office.HostType.Word) { + document.getElementById("sideload-msg").style.display = "none"; + document.getElementById("app-body").style.display = "flex"; + document.getElementById("run").onclick = runWord; + } +}); + +export async function runWord() { + return Word.run(async (context) => { + /** + * Insert your Word code here + */ + + // insert a paragraph at the end of the document. + const paragraph = context.document.body.insertParagraph("Hello World", Word.InsertLocation.end); + + // change the paragraph color to blue. + paragraph.font.color = "blue"; + + await context.sync(); + }); +} diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/tsconfig.json b/hello-world-teams-tab-and-office-add-in/add-in/tsconfig.json similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/add-in/tsconfig.json rename to hello-world-teams-tab-and-office-add-in/add-in/tsconfig.json diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/webpack.config.js b/hello-world-teams-tab-and-office-add-in/add-in/webpack.config.js similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/add-in/webpack.config.js rename to hello-world-teams-tab-and-office-add-in/add-in/webpack.config.js diff --git a/hello-world-teams-tab-and-outlook-add-in/appPackage/color.png b/hello-world-teams-tab-and-office-add-in/appPackage/color.png similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/appPackage/color.png rename to hello-world-teams-tab-and-office-add-in/appPackage/color.png diff --git a/hello-world-teams-tab-and-outlook-add-in/appPackage/manifest.json b/hello-world-teams-tab-and-office-add-in/appPackage/manifest.json similarity index 56% rename from hello-world-teams-tab-and-outlook-add-in/appPackage/manifest.json rename to hello-world-teams-tab-and-office-add-in/appPackage/manifest.json index 53f7481b5..18e5bc73d 100644 --- a/hello-world-teams-tab-and-outlook-add-in/appPackage/manifest.json +++ b/hello-world-teams-tab-and-office-add-in/appPackage/manifest.json @@ -3,7 +3,6 @@ "manifestVersion": "devPreview", "version": "1.0.0", "id": "${{TEAMS_APP_ID}}", - "packageName": "com.microsoft.teams.extension", "developer": { "name": "Contoso App, Inc.", "websiteUrl": "${{TAB_ENDPOINT}}", @@ -56,6 +55,10 @@ { "name": "MailboxItem.Read.User", "type": "Delegated" + }, + { + "name": "Document.ReadWrite.User", + "type": "Delegated" } ] } @@ -68,13 +71,10 @@ { "requirements": { "scopes": [ - "mail" - ], - "capabilities": [ - { - "name": "Mailbox", - "minVersion": "1.3" - } + "mail", + "workbook", + "document", + "presentation" ] }, "runtimes": [ @@ -87,6 +87,22 @@ } ] }, + "id": "TaskPaneRuntimeMail", + "type": "general", + "code": { + "page": "https://localhost:53000/taskpane.html" + }, + "lifetime": "short", + "actions": [ + { + "id": "TaskPaneRuntimeShowMail", + "type": "openPage", + "pinnable": false, + "view": "dashboard" + } + ] + }, + { "id": "TaskPaneRuntime", "type": "general", "code": { @@ -103,6 +119,14 @@ ] }, { + "requirements": { + "capabilities": [ + { + "name": "AddinCommands", + "minVersion": "1.1" + } + ] + }, "id": "CommandsRuntime", "type": "general", "code": { @@ -113,20 +137,107 @@ "actions": [ { "id": "action", - "type": "executeFunction", - "displayName": "action" + "type": "executeFunction" } ] } ], "ribbons": [ { + "requirements": { + "capabilities": [ + { + "name": "Mailbox", + "minVersion": "1.3" + } + ] + }, "contexts": [ "mailRead" ], "tabs": [ { "builtInTabId": "TabDefault", + "groups": [ + { + "id": "msgReadGroup", + "label": "Contoso Add-in", + "icons": [ + { + "size": 16, + "url": "https://localhost:53000/assets/icon-16.png" + }, + { + "size": 32, + "url": "https://localhost:53000/assets/icon-32.png" + }, + { + "size": 80, + "url": "https://localhost:53000/assets/icon-80.png" + } + ], + "controls": [ + { + "id": "msgReadOpenPaneButton", + "type": "button", + "label": "Show Taskpane", + "icons": [ + { + "size": 16, + "url": "https://localhost:53000/assets/icon-16.png" + }, + { + "size": 32, + "url": "https://localhost:53000/assets/icon-32.png" + }, + { + "size": 80, + "url": "https://localhost:53000/assets/icon-80.png" + } + ], + "supertip": { + "title": "Show Taskpane", + "description": "Opens a pane displaying all available properties." + }, + "actionId": "TaskPaneRuntimeShowMail" + }, + { + "id": "ActionButton", + "type": "button", + "label": "Perform an action", + "icons": [ + { + "size": 16, + "url": "https://localhost:53000/assets/icon-16.png" + }, + { + "size": 32, + "url": "https://localhost:53000/assets/icon-32.png" + }, + { + "size": 80, + "url": "https://localhost:53000/assets/icon-80.png" + } + ], + "supertip": { + "title": "Perform an action", + "description": "Perform an action when clicked." + }, + "actionId": "action" + } + ] + } + ] + } + ] + }, + { + "contexts": [ + "default" + ], + "tabs": [ + { + "builtInTabId": "TabHome", "groups": [ { "id": "msgReadGroup", diff --git a/hello-world-teams-tab-and-outlook-add-in/appPackage/outline.png b/hello-world-teams-tab-and-office-add-in/appPackage/outline.png similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/appPackage/outline.png rename to hello-world-teams-tab-and-office-add-in/appPackage/outline.png diff --git a/hello-world-teams-tab-and-outlook-add-in/assets/sample.json b/hello-world-teams-tab-and-office-add-in/assets/sample.json similarity index 75% rename from hello-world-teams-tab-and-outlook-add-in/assets/sample.json rename to hello-world-teams-tab-and-office-add-in/assets/sample.json index 47f4aa40b..d602a338c 100644 --- a/hello-world-teams-tab-and-outlook-add-in/assets/sample.json +++ b/hello-world-teams-tab-and-office-add-in/assets/sample.json @@ -1,17 +1,21 @@ [ { - "name": "officedev-teamsfx-samples-tab-hello-world-teams-tab-and-outlook-add-in", + "name": "officedev-teamsfx-samples-tab-hello-world-teams-tab-and-office-add-in", "source": "officeDev", - "title": "Hello World Teams Tab and Outlook add-in", - "shortDescription": "A hello world project that contains both Teams Tab and Outlook add-in capability.", - "url": "https://github.com/OfficeDev/TeamsFx-Samples/tree/dev/hello-world-teams-tab-and-outlook-add-in", + "title": "Hello World Teams Tab and Office add-in", + "shortDescription": "A hello world project that contains both Teams Tab and Office add-in capability.", + "url": "https://github.com/OfficeDev/TeamsFx-Samples/tree/dev/hello-world-teams-tab-and-office-add-in", "longDescription": [ - "A hello world project that contains both Teams Tab and Outlook add-in capability." + "A hello world project that contains both Teams Tab and Office add-in capability." ], "creationDateTime": "2023-03-02", - "updateDateTime": "2023-10-18", + "updateDateTime": "2024-11-29", "products": [ "Teams", + "Office", + "Word", + "Excel", + "PowerPoint", "Outlook" ], "metadata": [ @@ -32,8 +36,8 @@ { "type": "image", "order": 100, - "url": "https://raw.githubusercontent.com/OfficeDev/TeamsFx-Samples/dev/hello-world-teams-tab-and-outlook-add-in/assets/thumbnail.png", - "alt": "Hello World Teams Tab and Outlook add-in" + "url": "https://raw.githubusercontent.com/OfficeDev/TeamsFx-Samples/dev/hello-world-teams-tab-and-office-add-in/assets/thumbnail.png", + "alt": "Hello World Teams Tab and Office add-in" } ], "authors": [ diff --git a/hello-world-teams-tab-and-outlook-add-in/assets/thumbnail.png b/hello-world-teams-tab-and-office-add-in/assets/thumbnail.png similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/assets/thumbnail.png rename to hello-world-teams-tab-and-office-add-in/assets/thumbnail.png diff --git a/hello-world-teams-tab-and-outlook-add-in/env/.env.dev b/hello-world-teams-tab-and-office-add-in/env/.env.dev similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/env/.env.dev rename to hello-world-teams-tab-and-office-add-in/env/.env.dev diff --git a/hello-world-teams-tab-and-outlook-add-in/env/.env.local b/hello-world-teams-tab-and-office-add-in/env/.env.local similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/env/.env.local rename to hello-world-teams-tab-and-office-add-in/env/.env.local diff --git a/hello-world-teams-tab-and-outlook-add-in/images/outlook-addin-open.PNG b/hello-world-teams-tab-and-office-add-in/images/outlook-addin-open.PNG similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/images/outlook-addin-open.PNG rename to hello-world-teams-tab-and-office-add-in/images/outlook-addin-open.PNG diff --git a/hello-world-teams-tab-and-outlook-add-in/images/outlook-addin-taskpane.PNG b/hello-world-teams-tab-and-office-add-in/images/outlook-addin-taskpane.PNG similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/images/outlook-addin-taskpane.PNG rename to hello-world-teams-tab-and-office-add-in/images/outlook-addin-taskpane.PNG diff --git a/hello-world-teams-tab-and-office-add-in/images/outlook-debug.PNG b/hello-world-teams-tab-and-office-add-in/images/outlook-debug.PNG new file mode 100644 index 000000000..6cef6d927 Binary files /dev/null and b/hello-world-teams-tab-and-office-add-in/images/outlook-debug.PNG differ diff --git a/hello-world-teams-tab-and-office-add-in/images/teams-debug.PNG b/hello-world-teams-tab-and-office-add-in/images/teams-debug.PNG new file mode 100644 index 000000000..ade017f1e Binary files /dev/null and b/hello-world-teams-tab-and-office-add-in/images/teams-debug.PNG differ diff --git a/hello-world-teams-tab-and-outlook-add-in/images/teams-tab-app.PNG b/hello-world-teams-tab-and-office-add-in/images/teams-tab-app.PNG similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/images/teams-tab-app.PNG rename to hello-world-teams-tab-and-office-add-in/images/teams-tab-app.PNG diff --git a/hello-world-teams-tab-and-outlook-add-in/infra/azure.bicep b/hello-world-teams-tab-and-office-add-in/infra/azure.bicep similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/infra/azure.bicep rename to hello-world-teams-tab-and-office-add-in/infra/azure.bicep diff --git a/hello-world-teams-tab-and-outlook-add-in/infra/azure.parameters.json b/hello-world-teams-tab-and-office-add-in/infra/azure.parameters.json similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/infra/azure.parameters.json rename to hello-world-teams-tab-and-office-add-in/infra/azure.parameters.json diff --git a/hello-world-teams-tab-and-outlook-add-in/package.json b/hello-world-teams-tab-and-office-add-in/package.json similarity index 95% rename from hello-world-teams-tab-and-outlook-add-in/package.json rename to hello-world-teams-tab-and-office-add-in/package.json index 49107f981..a9d7bd771 100644 --- a/hello-world-teams-tab-and-outlook-add-in/package.json +++ b/hello-world-teams-tab-and-office-add-in/package.json @@ -19,7 +19,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "devDependencies": { - "@microsoft/teamsapp-cli": "^3.0.0", + "@microsoft/teamsapp-cli": "^3.0.5", "env-cmd": "^10.1.0" } } diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/index.html b/hello-world-teams-tab-and-office-add-in/tab/index.html similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/index.html rename to hello-world-teams-tab-and-office-add-in/tab/index.html diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/package.json b/hello-world-teams-tab-and-office-add-in/tab/package.json similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/package.json rename to hello-world-teams-tab-and-office-add-in/tab/package.json diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/public/deploy.png b/hello-world-teams-tab-and-office-add-in/tab/public/deploy.png similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/public/deploy.png rename to hello-world-teams-tab-and-office-add-in/tab/public/deploy.png diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/public/favicon.ico b/hello-world-teams-tab-and-office-add-in/tab/public/favicon.ico similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/public/favicon.ico rename to hello-world-teams-tab-and-office-add-in/tab/public/favicon.ico diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/public/hello.png b/hello-world-teams-tab-and-office-add-in/tab/public/hello.png similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/public/hello.png rename to hello-world-teams-tab-and-office-add-in/tab/public/hello.png diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/public/publish.png b/hello-world-teams-tab-and-office-add-in/tab/public/publish.png similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/public/publish.png rename to hello-world-teams-tab-and-office-add-in/tab/public/publish.png diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/App.tsx b/hello-world-teams-tab-and-office-add-in/tab/src/components/App.tsx similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/App.tsx rename to hello-world-teams-tab-and-office-add-in/tab/src/components/App.tsx diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/Privacy.tsx b/hello-world-teams-tab-and-office-add-in/tab/src/components/Privacy.tsx similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/Privacy.tsx rename to hello-world-teams-tab-and-office-add-in/tab/src/components/Privacy.tsx diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/Tab.tsx b/hello-world-teams-tab-and-office-add-in/tab/src/components/Tab.tsx similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/Tab.tsx rename to hello-world-teams-tab-and-office-add-in/tab/src/components/Tab.tsx diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/TabConfig.tsx b/hello-world-teams-tab-and-office-add-in/tab/src/components/TabConfig.tsx similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/TabConfig.tsx rename to hello-world-teams-tab-and-office-add-in/tab/src/components/TabConfig.tsx diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/TermsOfUse.tsx b/hello-world-teams-tab-and-office-add-in/tab/src/components/TermsOfUse.tsx similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/TermsOfUse.tsx rename to hello-world-teams-tab-and-office-add-in/tab/src/components/TermsOfUse.tsx diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/AddSSO.tsx b/hello-world-teams-tab-and-office-add-in/tab/src/components/sample/AddSSO.tsx similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/AddSSO.tsx rename to hello-world-teams-tab-and-office-add-in/tab/src/components/sample/AddSSO.tsx diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/Deploy.css b/hello-world-teams-tab-and-office-add-in/tab/src/components/sample/Deploy.css similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/Deploy.css rename to hello-world-teams-tab-and-office-add-in/tab/src/components/sample/Deploy.css diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/Deploy.tsx b/hello-world-teams-tab-and-office-add-in/tab/src/components/sample/Deploy.tsx similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/Deploy.tsx rename to hello-world-teams-tab-and-office-add-in/tab/src/components/sample/Deploy.tsx diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/EditCode.tsx b/hello-world-teams-tab-and-office-add-in/tab/src/components/sample/EditCode.tsx similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/EditCode.tsx rename to hello-world-teams-tab-and-office-add-in/tab/src/components/sample/EditCode.tsx diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/Publish.css b/hello-world-teams-tab-and-office-add-in/tab/src/components/sample/Publish.css similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/Publish.css rename to hello-world-teams-tab-and-office-add-in/tab/src/components/sample/Publish.css diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/Publish.tsx b/hello-world-teams-tab-and-office-add-in/tab/src/components/sample/Publish.tsx similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/Publish.tsx rename to hello-world-teams-tab-and-office-add-in/tab/src/components/sample/Publish.tsx diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/Welcome.css b/hello-world-teams-tab-and-office-add-in/tab/src/components/sample/Welcome.css similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/Welcome.css rename to hello-world-teams-tab-and-office-add-in/tab/src/components/sample/Welcome.css diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/Welcome.tsx b/hello-world-teams-tab-and-office-add-in/tab/src/components/sample/Welcome.tsx similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/components/sample/Welcome.tsx rename to hello-world-teams-tab-and-office-add-in/tab/src/components/sample/Welcome.tsx diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/index.css b/hello-world-teams-tab-and-office-add-in/tab/src/index.css similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/index.css rename to hello-world-teams-tab-and-office-add-in/tab/src/index.css diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/src/index.tsx b/hello-world-teams-tab-and-office-add-in/tab/src/index.tsx similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/src/index.tsx rename to hello-world-teams-tab-and-office-add-in/tab/src/index.tsx diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/tsconfig.app.json b/hello-world-teams-tab-and-office-add-in/tab/tsconfig.app.json similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/tsconfig.app.json rename to hello-world-teams-tab-and-office-add-in/tab/tsconfig.app.json diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/tsconfig.json b/hello-world-teams-tab-and-office-add-in/tab/tsconfig.json similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/tsconfig.json rename to hello-world-teams-tab-and-office-add-in/tab/tsconfig.json diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/tsconfig.node.json b/hello-world-teams-tab-and-office-add-in/tab/tsconfig.node.json similarity index 100% rename from hello-world-teams-tab-and-outlook-add-in/tab/tsconfig.node.json rename to hello-world-teams-tab-and-office-add-in/tab/tsconfig.node.json diff --git a/hello-world-teams-tab-and-outlook-add-in/tab/vite.config.ts b/hello-world-teams-tab-and-office-add-in/tab/vite.config.ts similarity index 90% rename from hello-world-teams-tab-and-outlook-add-in/tab/vite.config.ts rename to hello-world-teams-tab-and-office-add-in/tab/vite.config.ts index 75086e298..f59da272b 100644 --- a/hello-world-teams-tab-and-outlook-add-in/tab/vite.config.ts +++ b/hello-world-teams-tab-and-office-add-in/tab/vite.config.ts @@ -4,6 +4,9 @@ import fs from "fs"; export default defineConfig({ plugins: [react()], + define: { + global: 'globalThis', + }, server: { port: 53000, https: { diff --git a/hello-world-teams-tab-and-outlook-add-in/teamsapp.local.yml b/hello-world-teams-tab-and-office-add-in/teamsapp.local.yml similarity index 97% rename from hello-world-teams-tab-and-outlook-add-in/teamsapp.local.yml rename to hello-world-teams-tab-and-office-add-in/teamsapp.local.yml index d32094f99..e39b45378 100644 --- a/hello-world-teams-tab-and-outlook-add-in/teamsapp.local.yml +++ b/hello-world-teams-tab-and-office-add-in/teamsapp.local.yml @@ -4,7 +4,7 @@ version: v1.2 additionalMetadata: - sampleTag: TeamsFx-Samples:hello-world-teams-tab-and-outlook-add-in + sampleTag: TeamsFx-Samples:hello-world-teams-tab-and-office-add-in provision: # Creates a Teams app diff --git a/hello-world-teams-tab-and-outlook-add-in/teamsapp.yml b/hello-world-teams-tab-and-office-add-in/teamsapp.yml similarity index 98% rename from hello-world-teams-tab-and-outlook-add-in/teamsapp.yml rename to hello-world-teams-tab-and-office-add-in/teamsapp.yml index 21207c5b8..820bc9c2d 100644 --- a/hello-world-teams-tab-and-outlook-add-in/teamsapp.yml +++ b/hello-world-teams-tab-and-office-add-in/teamsapp.yml @@ -4,7 +4,7 @@ version: v1.5 additionalMetadata: - sampleTag: TeamsFx-Samples:hello-world-teams-tab-and-outlook-add-in + sampleTag: TeamsFx-Samples:hello-world-teams-tab-and-office-add-in environmentFolderPath: ./env diff --git a/hello-world-teams-tab-and-outlook-add-in/add-in/package.json b/hello-world-teams-tab-and-outlook-add-in/add-in/package.json deleted file mode 100644 index 8d451ad1f..000000000 --- a/hello-world-teams-tab-and-outlook-add-in/add-in/package.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "office-addin-taskpane", - "version": "0.0.1", - "repository": { - "type": "git", - "url": "https://github.com/OfficeDev/Office-Addin-TaskPane.git" - }, - "license": "MIT", - "config": { - "app_to_debug": "outlook", - "app_type_to_debug": "desktop", - "dev_server_port": 53000 - }, - "scripts": { - "build": "webpack --mode production", - "build:dev": "webpack --mode development", - "dev-server": "webpack serve --mode development", - "lint": "office-addin-lint check", - "lint:fix": "office-addin-lint fix", - "login": "office-addin-dev-settings m365-account login", - "logout": "office-addin-dev-settings m365-account logout", - "prettier": "office-addin-lint prettier", - "start": "office-addin-debugging start ../appPackage/build/appPackage.local.zip", - "start:desktop": "office-addin-debugging start ../appPackage/build/appPackage.local.zip desktop", - "start:web": "office-addin-debugging start ../appPackage/build/appPackage.local.zip web", - "stop": "office-addin-debugging stop ../appPackage/build/manifest.local.json", - "validate": "office-addin-manifest validate ../appPackage/build/manifest.local.json", - "watch": "webpack --mode development --watch" - }, - "dependencies": { - "core-js": "^3.9.1", - "regenerator-runtime": "^0.14.1" - }, - "devDependencies": { - "@babel/core": "^7.13.10", - "@babel/preset-typescript": "^7.13.0", - "@types/office-js": "^1.0.256", - "@types/office-runtime": "^1.0.23", - "acorn": "^8.5.0", - "babel-loader": "^8.2.2", - "copy-webpack-plugin": "^9.0.1", - "eslint-plugin-office-addins": "^2.1.4", - "file-loader": "^6.2.0", - "html-loader": "^4.1.0", - "html-webpack-plugin": "^5.5.0", - "office-addin-cli": "^1.5.4", - "office-addin-debugging": "^5.0.2", - "office-addin-dev-certs": "^1.11.3", - "office-addin-lint": "^2.2.4", - "office-addin-manifest": "^1.12.1", - "office-addin-prettier-config": "^1.2.0", - "os-browserify": "^0.3.0", - "process": "^0.11.10", - "source-map-loader": "^3.0.0", - "ts-loader": "^9.4.1", - "typescript": "^4.3.5", - "webpack": "^5.73.0", - "webpack-cli": "^4.8.0", - "webpack-dev-server": "4.7.4" - }, - "prettier": "office-addin-prettier-config", - "browserslist": [ - "ie 11" - ] -} diff --git a/hello-world-teams-tab-and-outlook-add-in/images/outlook-debug.PNG b/hello-world-teams-tab-and-outlook-add-in/images/outlook-debug.PNG deleted file mode 100644 index 99a375200..000000000 Binary files a/hello-world-teams-tab-and-outlook-add-in/images/outlook-debug.PNG and /dev/null differ diff --git a/hello-world-teams-tab-and-outlook-add-in/images/teams-debug.PNG b/hello-world-teams-tab-and-outlook-add-in/images/teams-debug.PNG deleted file mode 100644 index 276ec695c..000000000 Binary files a/hello-world-teams-tab-and-outlook-add-in/images/teams-debug.PNG and /dev/null differ diff --git a/intelligent-data-chart-generator/vite.config.ts b/intelligent-data-chart-generator/vite.config.ts index c2c269f45..bf2d61647 100644 --- a/intelligent-data-chart-generator/vite.config.ts +++ b/intelligent-data-chart-generator/vite.config.ts @@ -4,6 +4,9 @@ import fs from "fs"; export default defineConfig({ plugins: [react()], + define: { + global: 'globalThis', + }, server: { port: 53000, https: { diff --git a/large-scale-notification/.vscode/launch.json b/large-scale-notification/.vscode/launch.json index 5046ab233..648f48516 100644 --- a/large-scale-notification/.vscode/launch.json +++ b/large-scale-notification/.vscode/launch.json @@ -1,95 +1,28 @@ { - "version": "0.2.0", - "configurations": [ - { - "name": "Launch Remote (Edge)", - "type": "msedge", - "request": "launch", - "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", - "presentation": { - "group": "remote", - "order": 1 - }, - "internalConsoleOptions": "neverOpen" - }, - { - "name": "Launch Remote (Chrome)", - "type": "chrome", - "request": "launch", - "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", - "presentation": { - "group": "remote", - "order": 2 - }, - "internalConsoleOptions": "neverOpen" - }, - { - "name": "Launch App (Edge)", - "type": "msedge", - "request": "launch", - "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", - "cascadeTerminateToConfigurations": [ - "Attach to Local Service" - ], - "presentation": { - "group": "all", - "hidden": true - }, - "internalConsoleOptions": "neverOpen" - }, - { - "name": "Launch App (Chrome)", - "type": "chrome", - "request": "launch", - "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", - "cascadeTerminateToConfigurations": [ - "Attach to Local Service" - ], - "presentation": { - "group": "all", - "hidden": true - }, - "internalConsoleOptions": "neverOpen" - }, - { - "name": "Attach to Local Service", - "type": "node", - "request": "attach", - "port": 9239, - "restart": true, - "presentation": { - "group": "all", - "hidden": true - }, - "internalConsoleOptions": "neverOpen" - } - ], - "compounds": [ - { - "name": "Debug in Teams (Edge)", - "configurations": [ - "Launch App (Edge)", - "Attach to Local Service" - ], - "preLaunchTask": "Start Teams App Locally", - "presentation": { - "group": "all", - "order": 1 - }, - "stopAll": true - }, - { - "name": "Debug in Teams (Chrome)", - "configurations": [ - "Launch App (Chrome)", - "Attach to Local Service" - ], - "preLaunchTask": "Start Teams App Locally", - "presentation": { - "group": "all", - "order": 2 - }, - "stopAll": true - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Remote (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "remote", + "order": 1 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "remote", + "order": 2 + }, + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [] } diff --git a/large-scale-notification/.vscode/tasks.json b/large-scale-notification/.vscode/tasks.json deleted file mode 100644 index 138893313..000000000 --- a/large-scale-notification/.vscode/tasks.json +++ /dev/null @@ -1,172 +0,0 @@ -// This file is automatically generated by Teams Toolkit. -// The teamsfx tasks defined in this file require Teams Toolkit version >= 5.0.0. -// See https://aka.ms/teamsfx-tasks for details on how to customize each task. -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Start Teams App Locally", - "dependsOn": [ - "Validate prerequisites", - "Start local tunnel", - "Provision", - "Deploy", - "Start application" - ], - "dependsOrder": "sequence" - }, - { - // Check all required prerequisites. - // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. - "label": "Validate prerequisites", - "type": "teamsfx", - "command": "debug-check-prerequisites", - "args": { - "prerequisites": [ - "nodejs", // Validate if Node.js is installed. - "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission. - "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. - ], - "portOccupancy": [ - 3978, // app service port - 9239 // app inspector port for Node.js debugger - ] - } - }, - { - // Start the local tunnel service to forward public URL to local port and inspect traffic. - // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions. - "label": "Start local tunnel", - "type": "teamsfx", - "command": "debug-start-local-tunnel", - "args": { - "type": "dev-tunnel", - "ports": [ - { - "portNumber": 3978, - "protocol": "http", - "access": "public", - "writeToEnvironmentFile": { - "endpoint": "BOT_ENDPOINT", // output tunnel endpoint as BOT_ENDPOINT - "domain": "BOT_DOMAIN" // output tunnel domain as BOT_DOMAIN - } - } - ], - "env": "local" - }, - "isBackground": true, - "problemMatcher": "$teamsfx-local-tunnel-watch" - }, - { - // Create the debug resources. - // See https://aka.ms/teamsfx-tasks/provision to know the details and how to customize the args. - "label": "Provision", - "type": "teamsfx", - "command": "provision", - "args": { - "env": "local" - } - }, - { - // Build project. - // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. - "label": "Deploy", - "type": "teamsfx", - "command": "deploy", - "args": { - "env": "local" - } - }, - { - "label": "Start application", - "type": "shell", - "command": "npm run dev:teamsfx", - "isBackground": true, - "options": { - "cwd": "${workspaceFolder}", - "env": { - "PATH": "${workspaceFolder}/devTools/func:${env:PATH}" - } - }, - "windows": { - "options": { - "env": { - "PATH": "${workspaceFolder}/devTools/func;${env:PATH}" - } - } - }, - "problemMatcher": { - "pattern": { - "regexp": "^.*$", - "file": 0, - "location": 1, - "message": 2 - }, - "background": { - "activeOnStart": true, - "beginsPattern": "^.*(Job host stopped|signaling restart).*$", - "endsPattern": "^.*(Worker process started and initialized|Host lock lease acquired by instance ID).*$" - } - }, - "dependsOn": [ - "Create emulated table storage", - "Compile typescript" - ] - }, - { - "label": "Start Azurite emulator", - "type": "shell", - "command": "npm run prepare-storage:teamsfx", - "isBackground": true, - "problemMatcher": { - "pattern": [ - { - "regexp": "^.*$", - "file": 0, - "location": 1, - "message": 2 - } - ], - "background": { - "activeOnStart": true, - "beginsPattern": "Azurite", - "endsPattern": "successfully listening" - } - }, - "options": { - "cwd": "${workspaceFolder}" - }, - "presentation": { - "reveal": "silent" - } - }, - { - "label": "Create emulated table storage", - "type": "shell", - "command": "npx ts-node ./script/createTableForLocalDebug.ts", - "isBackground": true, - "options": { - "cwd": "${workspaceFolder}" - }, - "presentation": { - "reveal": "silent" - }, - "dependsOn": [ - "Start Azurite emulator" - ] - }, - { - "label": "Compile typescript", - "type": "shell", - "command": "npm run watch:teamsfx", - "isBackground": true, - "options": { - "cwd": "${workspaceFolder}" - }, - "problemMatcher": "$tsc-watch", - "presentation": { - "reveal": "silent" - } - } - ] -} \ No newline at end of file diff --git a/large-scale-notification/README.md b/large-scale-notification/README.md index 8e978efab..2df906bb7 100644 --- a/large-scale-notification/README.md +++ b/large-scale-notification/README.md @@ -16,30 +16,23 @@ extensions: This sample demonstrates the architecture of a Teams notfication bot app created by Teams Toolkit to send individual chat messages to a large number of users in a tenant. This app relies on Azure services such as [Durable Function](https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview?tabs=csharp-inproc) and [Service Bus Queue](https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-queues-topics-subscriptions#queues) to handle high volume and speed of notification messaging. -# How to run this project - -## Run the app locally - -To debug the project, you will need to configure an Azure Service Bus to be used locally: - -1. [Create a Service Bus namespace in the Azure portal](https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-quickstart-topics-subscriptions-portal#create-a-namespace-in-the-azure-portal). -2. Navigate to the homepage of your Service Bus namespace. Under the `Settings` section, select `Shared access policies`. Then, click on `RootManageSharedAccessKey` and copy the `Primary Connection String` value. - ![Copy the Value of Service Bus Connection String](./assets/ServiceBusConnectionString.png) -3. Open **local.settings.json** file and insert the connection string value you copied earlier into the `SERVICE_BUS_CONNECTION_STRING` field. -4. Return to the homepage of your Service Bus namespace and select "+ Queue". Proceed to create a queue with the "Max delivery count" configured to 1. - ![Service Bus Queue](./assets/ServiceBusQueue.png) -5. Open **env/.env.local** file, and set the value of `SERVICE_BUS_QUEUE_NAME` with the name of queue you just created. -6. Open **teamsapp.local.yml**, and substitue the `${{SECRET_STORAGE_ACCOUNT_KEY}}` with the well-known storage account key listed in the [guide](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio%2Cblob-storage#well-known-storage-account-and-key). -7. Press "F5" to open a browser window and then select your package to view the large scale notification bot app. -8. Get the endpoint of the trigger. For debug, `` is `http://localhost:3978` by default. -9. Navigate to `http://localhost:3978/api/notification` to activate the sending function. Then, access the `statusQueryGetUri` in the returned JSON object to retrieve the sending status. -10. \[Optional\] Once your app is running locally, you can utilize Azure Storage Explorer to inspect the data in your local storage table. Visit https://learn.microsoft.com/en-us/azure/storage/common/storage-explorer-emulators for more information. +# Minimal path to awesome ## Execute lifecycle commands 1. To create the Azure resources and deploy the code to Azure Function, select `Provision` and `Deploy` from the Teams Toolkit sidebar. 2. To publish your app to Teams, select `Publish` from the Teams Toolkit sidebar. +### Preview the app in Teams + +1. Once deployment is completed, you can preview the app running in Azure. In Visual Studio Code, open `Run and Debug` and select `Launch Remote (Edge)` or `Launch Remote (Chrome)` in the dropdown list and Press `F5` or green arrow button to open a browser. +2. Get the endpoint of the trigger. The `` can be found in `BOT_FUNCTION_ENDPOINT` of the file `env/.env.dev`. + +## Trigger send notification function + +1. Visit `https://{BOT_FUNCTION_ENDPOINT}/api/notification` in browser. This will trigger the function to send notifications. +2. Check the link of `statusQueryGetUri` in returned json object, it reflects the sending status of this invocation. + ## Install Teams App for all users 1. Visit https://admin.teams.microsoft.com/. Click "Manage apps" under "Teams apps" and find the app with name "large-scale-notifi-dev". @@ -61,15 +54,14 @@ To debug the project, you will need to configure an Azure Service Bus to be used Since there are usually at most 25 users in Microsoft 365 E3/E5 subscription in a tenant, duplicate the installation data can be used to mock a large amount of users. 1. Copy the value of `STORAGE_ACCOUNT_NAME` in `env/.env.dev` and paste it to variable `storageAccount` in `script/mockInstallationData.ts`. -2. Copy the value of `SECRET_STORAGE_ACCOUNT_KEY` in `env/.env.dev.user` by clicking "Decrypt secret" and paste it to variable `storageAccountKey` in `script/mockInstallationData.ts`. +2. Please ensure you are logged into the Azure account used for deploying the app. 3. Run command in project root folder: `npx ts-node script/mockInstallationData.ts`. 4. Update `storageTableName` in `src/internal/initialize.ts` to `installationMockTableName`. 5. Deploy the code to Azure Function by selecting `Deploy` from the Teams Toolkit sidebar. Your app should now use the mock data. -## Trigger send notification function +## Note on Local Debugging -1. Visit `https://{BOT_FUNCTION_ENDPOINT}/api/notification` in browser. This will trigger the function to send notifications. -2. Check the link of `statusQueryGetUri` in returned json object, it reflects the sending status of this invocation. +Local debugging is not supported for this project. Please ensure that all testing and debugging are conducted in the Azure environment to ensure compatibility and proper functionality. ## Architecture diff --git a/large-scale-notification/assets/ServiceBusConnectionString.png b/large-scale-notification/assets/ServiceBusConnectionString.png deleted file mode 100644 index f9d7564b7..000000000 Binary files a/large-scale-notification/assets/ServiceBusConnectionString.png and /dev/null differ diff --git a/large-scale-notification/assets/ServiceBusQueue.png b/large-scale-notification/assets/ServiceBusQueue.png deleted file mode 100644 index 832c4ae9a..000000000 Binary files a/large-scale-notification/assets/ServiceBusQueue.png and /dev/null differ diff --git a/large-scale-notification/assets/appDetailPage.png b/large-scale-notification/assets/appDetailPage.png index 5ee665877..6baca1d1a 100644 Binary files a/large-scale-notification/assets/appDetailPage.png and b/large-scale-notification/assets/appDetailPage.png differ diff --git a/large-scale-notification/enqueueTasksForInstallations/function.json b/large-scale-notification/enqueueTasksForInstallations/function.json deleted file mode 100644 index 808342054..000000000 --- a/large-scale-notification/enqueueTasksForInstallations/function.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "bindings": [ - { - "name": "input", - "type": "activityTrigger", - "direction": "in" - } - ], - "scriptFile": "../dist/src/functions/enqueueTasksForInstallationsActivity.js" -} \ No newline at end of file diff --git a/large-scale-notification/env/.env.dev b/large-scale-notification/env/.env.dev index e993aa137..84138b0f8 100644 --- a/large-scale-notification/env/.env.dev +++ b/large-scale-notification/env/.env.dev @@ -15,10 +15,11 @@ BOT_AZURE_FUNCTION_APP_RESOURCE_ID= BOT_DOMAIN= BOT_FUNCTION_ENDPOINT= TEAMS_APP_TENANT_ID= -SERVICE_BUS_ENDPOINT= SERVICE_BUS_QUEUE_NAME= STORAGE_ACCOUNT_NAME= -STORAGE_ACCOUNT_URL= INSTALLATION_TABLE_NAME= INSTALLATION_MOCK_TABLE_NAME= -TEAMS_APP_PUBLISHED_APP_ID= \ No newline at end of file +TEAMS_APP_PUBLISHED_APP_ID= +SERVICE_BUS_NAMESPACE= +MANAGED_IDENTITY_ID= +BOT_TENANT_ID= \ No newline at end of file diff --git a/large-scale-notification/env/.env.dev.user b/large-scale-notification/env/.env.dev.user deleted file mode 100644 index fc5f13bd2..000000000 --- a/large-scale-notification/env/.env.dev.user +++ /dev/null @@ -1,5 +0,0 @@ -# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. - -# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly -SECRET_SERVICE_BUS_ACCESS_KEY= -SECRET_STORAGE_ACCOUNT_KEY= \ No newline at end of file diff --git a/large-scale-notification/env/.env.local b/large-scale-notification/env/.env.local deleted file mode 100644 index 8785e3ffd..000000000 --- a/large-scale-notification/env/.env.local +++ /dev/null @@ -1,16 +0,0 @@ -# This file includes environment variables that will be committed to git by default. - -# Built-in environment variables -TEAMSFX_ENV=local -APP_NAME_SUFFIX=local -# Generated during provision, you can also add your own variables. -BOT_ID= -TEAMS_APP_ID= -BOT_DOMAIN= -BOT_ENDPOINT= -TEAMS_APP_TENANT_ID= -STORAGE_ACCOUNT_NAME= -STORAGE_ACCOUNT_URL= -FUNC_PATH= -SERVICE_BUS_QUEUE_NAME= -INSTALLATION_TABLE_NAME= \ No newline at end of file diff --git a/large-scale-notification/env/.env.local.user b/large-scale-notification/env/.env.local.user deleted file mode 100644 index 8a1f41638..000000000 --- a/large-scale-notification/env/.env.local.user +++ /dev/null @@ -1,5 +0,0 @@ -# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. - -# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly -SECRET_BOT_PASSWORD= -SECRET_STORAGE_ACCOUNT_KEY= \ No newline at end of file diff --git a/large-scale-notification/host.json b/large-scale-notification/host.json index 8e588272b..a3d1100ae 100644 --- a/large-scale-notification/host.json +++ b/large-scale-notification/host.json @@ -10,11 +10,16 @@ }, "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[2.*, 3.0.0)" + "version": "[4.*, 5.0.0)" }, "extensions": { - "http": { - "routePrefix": "" + "serviceBus": { + "clientRetryOptions": { + "mode": "exponential", + "maxRetries": 3, + "delay": "00:00:00.80", + "maxDelay": "00:00:19" + } } } } diff --git a/large-scale-notification/infra/azure.bicep b/large-scale-notification/infra/azure.bicep index 388759c46..65d0ad9d1 100644 --- a/large-scale-notification/infra/azure.bicep +++ b/large-scale-notification/infra/azure.bicep @@ -24,6 +24,72 @@ resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' name: identityName } +// https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles +var StorageBlobDataOwner = 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b' +var StorageQueueDataContributor = '974c5e8b-45b9-4653-ba55-5f855dd0fb88' +var StorageTableDataContributor = '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3' +var AzureServiceBusDataOwner = '090c5cfd-751d-490a-894a-3ce6f1109419' + +resource storageBlobRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('${resourceBaseName}-${uniqueString('StorageBlobRoleAssignment')}') + scope: storage + properties: { + principalId: functionApp.identity.principalId + principalType: 'ServicePrincipal' + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', StorageBlobDataOwner) + } +} + +resource storageQueueRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('${resourceBaseName}-${uniqueString('StorageQueueRoleAssignment')}') + scope: storage + properties: { + principalId: functionApp.identity.principalId + principalType: 'ServicePrincipal' + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', StorageQueueDataContributor) + } +} + +resource storageTableRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('${resourceBaseName}-${uniqueString('StorageTableRoleAssignment')}') + scope: storage + properties: { + principalId: functionApp.identity.principalId + principalType: 'ServicePrincipal' + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', StorageTableDataContributor) + } +} + +resource storageTableUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('${resourceBaseName}-${uniqueString('StorageTableUserRoleAssignment')}') + scope: storage + properties: { + principalId: identity.properties.principalId + principalType: 'ServicePrincipal' + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', StorageTableDataContributor) + } +} + +resource azureServiceBusRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('${resourceBaseName}-${uniqueString('AzureServiceBusRoleAssignment')}') + scope: serviceBusNamespace + properties: { + principalId: functionApp.identity.principalId + principalType: 'ServicePrincipal' + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', AzureServiceBusDataOwner) + } +} + +resource azureServiceBusUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid('${resourceBaseName}-${uniqueString('AzureServiceBusUserRoleAssignment')}') + scope: serviceBusNamespace + properties: { + principalId: identity.properties.principalId + principalType: 'ServicePrincipal' + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', AzureServiceBusDataOwner) + } +} + // Compute resources for your Web App resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { kind: 'functionapp' @@ -43,6 +109,9 @@ resource storage 'Microsoft.Storage/storageAccounts@2021-06-01' = { sku: { name: storageSKU // You can follow https://aka.ms/teamsfx-bicep-add-param-tutorial to add functionStorageSku property to provisionParameters to override the default value "Standard_LRS". } + properties: { + allowSharedKeyAccess: false // Disable Shared Key Access + } } // Table storage or installation reference @@ -67,7 +136,9 @@ resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces@2022-01-01-preview sku: { name: 'Standard' } - properties: {} + properties: { + disableLocalAuth: true + } } // Service Bus queue for message sending @@ -101,12 +172,12 @@ resource functionApp 'Microsoft.Web/sites@2021-02-01' = { alwaysOn: false appSettings: [ { - name: 'AzureWebJobsDashboard' - value: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${listKeys(storage.id, storage.apiVersion).keys[0].value};EndpointSuffix=${environment().suffixes.storage}' // Azure Functions internal setting + name: 'AzureWebJobsStorage__accountName' + value: storageName } { - name: 'AzureWebJobsStorage' - value: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${listKeys(storage.id, storage.apiVersion).keys[0].value};EndpointSuffix=${environment().suffixes.storage}' // Azure Functions internal setting + name: 'ServiceBusConnection__fullyQualifiedNamespace' + value: '${serviceBusNamespaceName}.servicebus.windows.net' } { name: 'FUNCTIONS_EXTENSION_VERSION' @@ -116,10 +187,6 @@ resource functionApp 'Microsoft.Web/sites@2021-02-01' = { name: 'FUNCTIONS_WORKER_RUNTIME' value: 'node' // Set runtime to NodeJS } - { - name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' - value: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${listKeys(storage.id, storage.apiVersion).keys[0].value};EndpointSuffix=${environment().suffixes.storage}' // Azure Functions internal setting - } { name: 'WEBSITE_RUN_FROM_PACKAGE' value: '1' // Run Azure Functions from a package file @@ -141,8 +208,8 @@ resource functionApp 'Microsoft.Web/sites@2021-02-01' = { value: 'UserAssignedMsi' } { - name: 'SERVICE_BUS_CONNECTION_STRING' - value: 'Endpoint=sb://${serviceBusNamespaceName}.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=${listKeys('${serviceBusNamespace.id}/AuthorizationRules/RootManageSharedAccessKey', serviceBusNamespace.apiVersion).primaryKey}' + name: 'SERVICE_BUS_NAMESPACE' + value: serviceBusNamespaceName } { name: 'SERVICE_BUS_QUEUE_NAME' @@ -160,14 +227,6 @@ resource functionApp 'Microsoft.Web/sites@2021-02-01' = { name: 'STORAGE_ACCOUNT_NAME' value: storage.name } - { - name: 'STORAGE_ACCOUNT_URL' - value: 'https://${storage.name}.table.core.windows.net' - } - { - name: 'STORAGE_ACCOUNT_KEY' - value: listKeys(storage.id, storage.apiVersion).keys[0].value - } { name: 'RUNNING_ON_AZURE' value: '1' @@ -176,12 +235,16 @@ resource functionApp 'Microsoft.Web/sites@2021-02-01' = { name: 'SCM_ZIPDEPLOY_DONOT_PRESERVE_FILETIME' value: '1' // Zipdeploy files will always be updated. Detail: https://aka.ms/teamsfx-zipdeploy-donot-preserve-filetime } + { + name: 'MANAGED_IDENTITY_ID' + value: identity.properties.clientId + } ] ftpsState: 'FtpsOnly' } } identity: { - type: 'UserAssigned' + type: 'SystemAssigned, UserAssigned' userAssignedIdentities: { '${identity.id}': {} } @@ -204,13 +267,11 @@ module azureBotRegistration './botRegistration/azurebot.bicep' = { output BOT_DOMAIN string = functionApp.properties.defaultHostName output BOT_AZURE_FUNCTION_APP_RESOURCE_ID string = functionApp.id output BOT_FUNCTION_ENDPOINT string = 'https://${functionApp.properties.defaultHostName}' -output SERVICE_BUS_ENDPOINT string = 'Endpoint=sb://${serviceBusNamespaceName}.servicebus.windows.net/' -output SECRET_SERVICE_BUS_ACCESS_KEY string = listKeys('${serviceBusNamespace.id}/AuthorizationRules/RootManageSharedAccessKey', serviceBusNamespace.apiVersion).primaryKey +output SERVICE_BUS_NAMESPACE string = serviceBusNamespaceName output SERVICE_BUS_QUEUE_NAME string = serviceBusQueue.name output STORAGE_ACCOUNT_NAME string = storage.name -output STORAGE_ACCOUNT_URL string = 'https://${storage.name}.table.core.windows.net' -output SECRET_STORAGE_ACCOUNT_KEY string = listKeys(storage.id, storage.apiVersion).keys[0].value output INSTALLATION_TABLE_NAME string = storageTableName output INSTALLATION_MOCK_TABLE_NAME string = '${storageTableName}mock' +output MANAGED_IDENTITY_ID string = identity.properties.clientId output BOT_ID string = identity.properties.clientId output BOT_TENANT_ID string = identity.properties.tenantId diff --git a/large-scale-notification/local.settings.json b/large-scale-notification/local.settings.json deleted file mode 100644 index e007e3db3..000000000 --- a/large-scale-notification/local.settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "FUNCTIONS_WORKER_RUNTIME": "node", - "SERVICE_BUS_CONNECTION_STRING": "" - } -} diff --git a/large-scale-notification/messageHandler/function.json b/large-scale-notification/messageHandler/function.json deleted file mode 100644 index 842b0a9d6..000000000 --- a/large-scale-notification/messageHandler/function.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "post" - ], - "route": "api/messages" - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ], - "scriptFile": "../dist/src/internal/messageHandler.js" -} diff --git a/large-scale-notification/notifyHttpTrigger/function.json b/large-scale-notification/notifyHttpTrigger/function.json deleted file mode 100644 index d5842339c..000000000 --- a/large-scale-notification/notifyHttpTrigger/function.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "post", - "get" - ], - "route": "api/notification" - }, - { - "type": "http", - "direction": "out", - "name": "$return" - }, - { - "name": "starter", - "type": "orchestrationClient", - "direction": "in" - } - ], - "scriptFile": "../dist/src/functions/durableFunctionHttpTrigger.js" -} diff --git a/large-scale-notification/package.json b/large-scale-notification/package.json index 454d8806c..48df972a7 100644 --- a/large-scale-notification/package.json +++ b/large-scale-notification/package.json @@ -3,14 +3,14 @@ "version": "1.0.0", "description": "Microsoft Teams Toolkit Large Scale Notification Bot Sample", "engines": { - "node": "16 || 18" + "node": "18 || 20" }, "author": "Microsoft", "license": "MIT", + "main": "dist/src/functions/*.js", "scripts": { "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev", "dev": "func start --typescript --language-worker=\"--inspect=9239\" --port \"3978\" --cors \"*\"", - "prepare-storage:teamsfx": "azurite --silent --location ./_storage_emulator --debug ./_storage_emulator/debug.log", "watch:teamsfx": "tsc --watch", "build": "tsc && shx cp -r ./src/adaptiveCards ./dist/src", "watch": "tsc -w", @@ -24,20 +24,19 @@ }, "dependencies": { "@azure/data-tables": "^13.2.2", + "@azure/functions": "^4.6.0", "@azure/service-bus": "^7.9.0", "adaptivecards-templating": "^2.3.1", "adaptive-expressions": "^4.22.3", - "@microsoft/teamsfx": "^2.3.0", - "botbuilder": "^4.18.0", - "durable-functions": "^2.1.2", + "@microsoft/teamsfx": "^3.0.0-alpha", + "botbuilder": "^4.23.1", + "durable-functions": "^3.1.0", "luxon": "^3.3.0" }, "devDependencies": { - "@azure/functions": "^3.5.0", "@types/json-schema": "^7.0.15", "@types/luxon": "^3.3.0", "axios": "^1.4.0", - "azurite": "^3.16.0", "env-cmd": "^10.1.0", "shx": "^0.3.4", "ts-node": "^10.4.0", diff --git a/large-scale-notification/script/createTableForLocalDebug.ts b/large-scale-notification/script/createTableForLocalDebug.ts deleted file mode 100644 index b61e6f6a9..000000000 --- a/large-scale-notification/script/createTableForLocalDebug.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - TableServiceClient, - AzureNamedKeyCredential, -} from "@azure/data-tables"; - -// Well-known storage account and key used by the legacy Azure Storage Emulator and Azurite -const account = "devstoreaccount1"; -const accountKey = - "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="; - -const credential = new AzureNamedKeyCredential(account, accountKey); -const tableServiceClient = new TableServiceClient( - "http://127.0.0.1:10002/devstoreaccount1", - credential, - { allowInsecureConnection: true } -); - -const tableName = "localtable"; - -async function main() { - await tableServiceClient.createTable(tableName, { - onResponse: (response) => { - if (response.status === 409) { - console.log(`Table ${tableName} already exists`); - } - }, - }); -} - -main(); diff --git a/large-scale-notification/script/mockInstallationData.ts b/large-scale-notification/script/mockInstallationData.ts index 498f28688..dee50591f 100644 --- a/large-scale-notification/script/mockInstallationData.ts +++ b/large-scale-notification/script/mockInstallationData.ts @@ -1,26 +1,23 @@ -import { AzureNamedKeyCredential, TableClient } from "@azure/data-tables"; +import { TableClient } from "@azure/data-tables"; +import { DefaultAzureCredential } from "@azure/identity"; -export const storageAccount = ""; -export const storageAccountKey = ""; +export const storageAccount = ""; const storageTableName = "installation"; const installationMockTableName = "installationmock"; async function copy() { - const sharedKeyCredential = new AzureNamedKeyCredential( - storageAccount, - storageAccountKey - ); + const credential = new DefaultAzureCredential(); const originInstallationTableClient = new TableClient( `https://${storageAccount}.table.core.windows.net`, `${storageTableName}`, - sharedKeyCredential + credential ); const mockInstallationTableClient = new TableClient( `https://${storageAccount}.table.core.windows.net`, `${installationMockTableName}`, - sharedKeyCredential + credential ); const pages = await originInstallationTableClient diff --git a/large-scale-notification/sendNotificationQueueTrigger/function.json b/large-scale-notification/sendNotificationQueueTrigger/function.json deleted file mode 100644 index 54c174eef..000000000 --- a/large-scale-notification/sendNotificationQueueTrigger/function.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "bindings": [ - { - "name": "mySbMsg", - "type": "serviceBusTrigger", - "direction": "in", - "queueName": "notification-messages", - "connection": "SERVICE_BUS_CONNECTION_STRING" - } - ], - "retry": { - "strategy": "exponentialBackoff", - "maxRetryCount": 3, - "minimumInterval": "00:00:01", - "maximumInterval": "00:00:19" - }, - "scriptFile": "../dist/src/functions/sendNotificationQueueTrigger.js" -} \ No newline at end of file diff --git a/large-scale-notification/sendNotifications/function.json b/large-scale-notification/sendNotifications/function.json deleted file mode 100644 index 64711438d..000000000 --- a/large-scale-notification/sendNotifications/function.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "bindings": [ - { - "name": "startTime", - "type": "orchestrationTrigger", - "direction": "in" - } - ], - "scriptFile": "../dist/src/functions/sendNotificationsOrchestrator.js" -} \ No newline at end of file diff --git a/large-scale-notification/src/cardModels.ts b/large-scale-notification/src/cardModels.ts deleted file mode 100644 index ef2daf802..000000000 --- a/large-scale-notification/src/cardModels.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Adaptive Card data model. Properties can be referenced in an adaptive card via the `${var}` - * Adaptive Card syntax. - */ -export interface CardData { - title: string; - appName: string; - description: string; - notificationUrl: string; -} diff --git a/large-scale-notification/src/consts.ts b/large-scale-notification/src/consts.ts index 0392ad82f..99f2f0cd2 100644 --- a/large-scale-notification/src/consts.ts +++ b/large-scale-notification/src/consts.ts @@ -1,14 +1,13 @@ -export const serviceBusQueueConnectionString = - process.env.SERVICE_BUS_CONNECTION_STRING; +export const serviceBusNamespace = process.env.SERVICE_BUS_NAMESPACE; export const serviceBusMessageQueueName = process.env.SERVICE_BUS_QUEUE_NAME; -export const storageAccount = process.env.STORAGE_ACCOUNT_NAME; -export const storageAccountURL = process.env.STORAGE_ACCOUNT_URL; -export const storageAccountKey = process.env.STORAGE_ACCOUNT_KEY; +export const storageAccountName = process.env.STORAGE_ACCOUNT_NAME; export const storageTableName = process.env.INSTALLATION_TABLE_NAME; export const installationMockTableName = process.env.INSTALLATION_MOCK_TABLE_NAME; +export const managedIdentityId = process.env.MANAGED_IDENTITY_ID; + // Sending speed of notification message export const RPS = 50; // Time(second) to wait before sending the next batch of messages to queue diff --git a/large-scale-notification/src/functions/durableFunctionHttpTrigger.ts b/large-scale-notification/src/functions/durableFunctionHttpTrigger.ts index a20c8f691..44f509f7b 100644 --- a/large-scale-notification/src/functions/durableFunctionHttpTrigger.ts +++ b/large-scale-notification/src/functions/durableFunctionHttpTrigger.ts @@ -1,30 +1,48 @@ import * as df from "durable-functions"; -import { AzureFunction, Context, HttpRequest } from "@azure/functions"; +import { DefaultAzureCredential } from "@azure/identity"; +import { + app, + HttpHandler, + HttpRequest, + HttpResponse, + InvocationContext, +} from "@azure/functions"; import { ServiceBusAdministrationClient } from "@azure/service-bus"; import { + managedIdentityId, serviceBusMessageQueueName, - serviceBusQueueConnectionString, + serviceBusNamespace, } from "../consts"; -const httpStart: AzureFunction = async function ( - context: Context, - req: HttpRequest -): Promise { +const durableHttpStart: HttpHandler = async ( + request: HttpRequest, + context: InvocationContext +): Promise => { const client = df.getClient(context); + const credential = new DefaultAzureCredential({ + managedIdentityClientId: managedIdentityId, + }); const sbAdminClient = new ServiceBusAdministrationClient( - serviceBusQueueConnectionString + `${serviceBusNamespace}.servicebus.windows.net`, + credential ); let queueRuntimeProperties = await sbAdminClient.getQueueRuntimeProperties( serviceBusMessageQueueName ); - const instanceId = await client.startNew("sendNotifications", undefined, { - startTime: new Date(), - initDeadLetterMessageCount: queueRuntimeProperties.deadLetterMessageCount, + const instanceId = await client.startNew("sendNotifications", { + instanceId: undefined, + input: { + startTime: new Date(), + initDeadLetterMessageCount: queueRuntimeProperties.deadLetterMessageCount, + }, }); - context.log(`Started orchestration with ID = '${instanceId}'.`); + context.log(`Started orchestration with ID = '{instanceId}'.`); - return client.createCheckStatusResponse(context.bindingData.req, instanceId); + return client.createCheckStatusResponse(request, instanceId); }; -export default httpStart; +app.http("notification", { + extraInputs: [df.input.durableClient()], + handler: durableHttpStart, +}); diff --git a/large-scale-notification/src/functions/enqueueTasksForInstallationsActivity.ts b/large-scale-notification/src/functions/enqueueTasksForInstallationsActivity.ts index 03ba5779f..0f865ff78 100644 --- a/large-scale-notification/src/functions/enqueueTasksForInstallationsActivity.ts +++ b/large-scale-notification/src/functions/enqueueTasksForInstallationsActivity.ts @@ -2,11 +2,14 @@ * This function is not intended to be invoked directly. Instead it will be * triggered by an orchestrator function. */ +import * as df from "durable-functions"; +import { ActivityHandler } from "durable-functions"; import { chunk } from "lodash"; import { DateTime } from "luxon"; -import { AzureFunction, Context } from "@azure/functions"; +import { DefaultAzureCredential } from "@azure/identity"; +import { InvocationContext } from "@azure/functions"; import { ServiceBusAdministrationClient, ServiceBusClient, @@ -16,28 +19,33 @@ import { BotBuilderCloudAdapter } from "@microsoft/teamsfx"; import { batchSendingInterval, iterateTime, + managedIdentityId, maxPageSize, RPS, serviceBusMessageQueueName, - serviceBusQueueConnectionString, + serviceBusNamespace, } from "../consts"; import { notificationApp } from "../internal/initialize"; import { SendStatus } from "../types/sendStatus"; import { extractKeyDataFromConversationReference } from "../util"; -const enqueueTasksForInstallationsActivity: AzureFunction = async function ( - context: Context -): Promise<{ sendStatus: SendStatus; continuationToken: string }> { - const input = context.bindings.input as { +const enqueueTasksForInstallationsActivity: ActivityHandler = async ( + triggerInput: any, + context: InvocationContext +): Promise<{ sendStatus: SendStatus; continuationToken: string }> => { + const input = triggerInput as { continuationToken: string; sendStatus: SendStatus; }; + const credential = new DefaultAzureCredential({ + managedIdentityClientId: managedIdentityId, + }); let token = input.continuationToken; let installations: BotBuilderCloudAdapter.TeamsBotInstallation[] = []; let lastSendTime: Date = undefined; let newStatus = { ...input.sendStatus }; for (let iter = 0; iter < iterateTime; iter++) { - context.log.warn( + context.warn( `${new Date().toISOString()} #${iter} [enqueueTasksForInstallationsActivity] continue ${token}` ); const installationResult = @@ -53,14 +61,17 @@ const enqueueTasksForInstallationsActivity: AzureFunction = async function ( break; } - context.log.warn( + context.warn( `${new Date().toISOString()} #${iter} [enqueueTasksForInstallationsActivity] found ${ installations.length } installations` ); newStatus.totalMessageCount += installations.length; - const sbClient = new ServiceBusClient(serviceBusQueueConnectionString); + const sbClient = new ServiceBusClient( + `${serviceBusNamespace}.servicebus.windows.net`, + credential + ); const sender = sbClient.createSender(serviceBusMessageQueueName); const chunks = chunk(installations, RPS * batchSendingInterval); for (const chunk of chunks) { @@ -83,12 +94,12 @@ const enqueueTasksForInstallationsActivity: AzureFunction = async function ( }) .plus({ second: batchSendingInterval }) .toJSDate(); - context.log.warn( + context.warn( `[enqueueTasksForInstallationsActivity] ${new Date().toISOString()} next enqueue time ${nextEnqueueTime.toISOString()}` ); if (nextEnqueueTime > new Date()) { const waitMs = nextEnqueueTime.getTime() - new Date().getTime(); - context.log.warn( + context.warn( `[enqueueTasksForInstallationsActivity] wait to ${nextEnqueueTime.toISOString()}, ${waitMs} ms` ); await new Promise((r) => setTimeout(r, waitMs)); @@ -96,13 +107,13 @@ const enqueueTasksForInstallationsActivity: AzureFunction = async function ( } lastSendTime = new Date(); - context.log.warn( + context.warn( `[enqueueTasksForInstallationsActivity] ${lastSendTime.toISOString()} sending ${ chunk[0].conversationReference.user.id }` ); await sender.sendMessages(batch); - context.log.warn( + context.warn( `[enqueueTasksForInstallationsActivity] sent task to queue ${ chunk[0].conversationReference.user.id }, cost ${new Date().getTime() - lastSendTime.getTime()} ms}` @@ -113,13 +124,15 @@ const enqueueTasksForInstallationsActivity: AzureFunction = async function ( break; } } + const sbAdminClient = new ServiceBusAdministrationClient( - serviceBusQueueConnectionString + `${serviceBusNamespace}.servicebus.windows.net`, + credential ); const runtimeProperties = await sbAdminClient.getQueueRuntimeProperties( serviceBusMessageQueueName ); - context.log.warn( + context.warn( `[enqueueTasksForInstallationsActivity] active messages: ${runtimeProperties.activeMessageCount}` ); newStatus.sentMessageCount = @@ -130,4 +143,6 @@ const enqueueTasksForInstallationsActivity: AzureFunction = async function ( return { continuationToken: token, sendStatus: newStatus }; }; -export default enqueueTasksForInstallationsActivity; +df.app.activity("enqueueTasksForInstallations", { + handler: enqueueTasksForInstallationsActivity, +}); diff --git a/large-scale-notification/src/functions/messageHandler.ts b/large-scale-notification/src/functions/messageHandler.ts new file mode 100644 index 000000000..c8fbb63ef --- /dev/null +++ b/large-scale-notification/src/functions/messageHandler.ts @@ -0,0 +1,47 @@ +import { + app, + HttpRequest, + HttpResponseInit, + InvocationContext, +} from "@azure/functions"; +import { notificationApp } from "../internal/initialize"; +import { Request, Response } from "botbuilder"; + +export async function messages( + request: HttpRequest, + context: InvocationContext +): Promise { + const res: HttpResponseInit = { status: 200 }; + const response = { + end: () => {}, + header: (name: string, value: unknown) => { + res.headers = res.headers || {}; + res.headers[name] = value; + }, + send: (body: unknown) => { + res.body = body as string; + }, + status: (code) => { + res.status = code; + }, + socket: {}, + } as Response; + await notificationApp.requestHandler(await requestAdaptor(request), response); + return res; +} + +async function requestAdaptor(request: HttpRequest): Promise { + return { + body: (await request.json()) as any, + headers: (await Promise.all(request.headers.entries())).reduce( + (acc, [key, value]) => { + acc[key] = value; + return acc; + }, + {} + ), + method: request.method, + }; +} + +app.http("messages", { handler: messages }); diff --git a/large-scale-notification/src/functions/sendNotificationQueueTrigger.ts b/large-scale-notification/src/functions/sendNotificationQueueTrigger.ts index b88a67c25..42ce7c53f 100644 --- a/large-scale-notification/src/functions/sendNotificationQueueTrigger.ts +++ b/large-scale-notification/src/functions/sendNotificationQueueTrigger.ts @@ -1,16 +1,17 @@ import { TurnContext } from "botbuilder"; - -import { AzureFunction, Context } from "@azure/functions"; +import * as ACData from "adaptivecards-templating"; +import { app, InvocationContext } from "@azure/functions"; import { BotBuilderCloudAdapter } from "@microsoft/teamsfx"; import config from "../internal/config"; import { notificationApp } from "../internal/initialize"; import { InstallationReference } from "../types/installationReference"; +import notificationTemplate from "../adaptiveCards/notification-default.json"; import { constructConversationReference } from "../util"; -const serviceBusQueueTrigger: AzureFunction = async function ( - context: Context, - mySbMsg: any +export async function serviceBusQueueTrigger( + mySbMsg: any, + context: InvocationContext ): Promise { const installationReference = JSON.parse(mySbMsg) as InstallationReference; const conversation = constructConversationReference(installationReference); @@ -20,12 +21,23 @@ const serviceBusQueueTrigger: AzureFunction = async function ( config.MicrosoftAppId ); - await installation.sendMessage( - "Hello World!", + await installation.sendAdaptiveCard( + new ACData.Template(notificationTemplate).expand({ + $root: { + title: "New Event Occurred!", + appName: "Contoso App Notification", + description: `This is a sample http-triggered notification to ${installation.type}`, + notificationUrl: "https://aka.ms/teamsfx-notification-new", + }, + }), (_: TurnContext, error: Error) => { throw error; } ); -}; +} -export default serviceBusQueueTrigger; +app.serviceBusQueue("serviceBusQueueTrigger", { + connection: "ServiceBusConnection", + queueName: "notification-messages", + handler: serviceBusQueueTrigger, +}); diff --git a/large-scale-notification/src/functions/sendNotificationsOrchestrator.ts b/large-scale-notification/src/functions/sendNotificationsOrchestrator.ts index 785735104..2ec8fdbac 100644 --- a/large-scale-notification/src/functions/sendNotificationsOrchestrator.ts +++ b/large-scale-notification/src/functions/sendNotificationsOrchestrator.ts @@ -4,10 +4,13 @@ */ import * as df from "durable-functions"; +import { OrchestrationContext, OrchestrationHandler } from "durable-functions"; import { SendStatus } from "../types/sendStatus"; -const sendNotificationsOrchestrator = df.orchestrator(function* (context) { +const sendNotificationsOrchestrator: OrchestrationHandler = function* ( + context: OrchestrationContext +) { const input = context.df.getInput() as { startTime: string; initDeadLetterMessageCount: number; @@ -44,13 +47,13 @@ const sendNotificationsOrchestrator = df.orchestrator(function* (context) { } while (continuationToken !== undefined); sendStatus = yield context.df.callActivity("waitSendingFinish", sendStatus); - context.log.warn("[sendNotificationsOrchestrator] finish"); + context.warn("[sendNotificationsOrchestrator] finish"); context.df.setCustomStatus(undefined); return { duration: (context.df.currentUtcDateTime.getTime() - startTime.getTime()) / 1000, ...sendStatus, }; -}); +}; -export default sendNotificationsOrchestrator; +df.app.orchestration("sendNotifications", sendNotificationsOrchestrator); diff --git a/large-scale-notification/src/functions/waitSendingFinishActivity.ts b/large-scale-notification/src/functions/waitSendingFinishActivity.ts index b21a2db4d..fd0d2f4ce 100644 --- a/large-scale-notification/src/functions/waitSendingFinishActivity.ts +++ b/large-scale-notification/src/functions/waitSendingFinishActivity.ts @@ -8,29 +8,38 @@ * - run 'npm install durable-functions' from the wwwroot folder of your * function app in Kudu */ +import * as df from "durable-functions"; +import { ActivityHandler } from "durable-functions"; -import { AzureFunction, Context } from "@azure/functions"; +import { DefaultAzureCredential } from "@azure/identity"; +import { InvocationContext } from "@azure/functions"; import { ServiceBusAdministrationClient } from "@azure/service-bus"; import { + managedIdentityId, serviceBusMessageQueueName, - serviceBusQueueConnectionString, + serviceBusNamespace, } from "../consts"; import { SendStatus } from "../types/sendStatus"; -const waitSendingFinishActivity: AzureFunction = async function ( - context: Context -): Promise { - const input = context.bindings.input as SendStatus; +const waitSendingFinishActivity: ActivityHandler = async ( + triggerInput: any, + context: InvocationContext +): Promise => { + const credential = new DefaultAzureCredential({ + managedIdentityClientId: managedIdentityId, + }); + const input = triggerInput as SendStatus; const sbAdminClient = new ServiceBusAdministrationClient( - serviceBusQueueConnectionString + `${serviceBusNamespace}.servicebus.windows.net`, + credential ); let runtimeProperties = await sbAdminClient.getQueueRuntimeProperties( serviceBusMessageQueueName ); - context.log.warn(`[waitSendingFinishActivity] checking.`); + context.warn(`[waitSendingFinishActivity] checking.`); while (runtimeProperties.activeMessageCount > 0) { - context.log.warn( + context.warn( `[waitSendingFinishActivity] active messages: ${runtimeProperties.activeMessageCount}` ); await new Promise((r) => setTimeout(r, 5000)); @@ -44,4 +53,6 @@ const waitSendingFinishActivity: AzureFunction = async function ( return input; }; -export default waitSendingFinishActivity; +df.app.activity("waitSendingFinish", { + handler: waitSendingFinishActivity, +}); diff --git a/large-scale-notification/src/httpTrigger.ts b/large-scale-notification/src/httpTrigger.ts deleted file mode 100644 index 9a40223b0..000000000 --- a/large-scale-notification/src/httpTrigger.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { AzureFunction, Context, HttpRequest } from "@azure/functions"; -import * as ACData from "adaptivecards-templating"; -import notificationTemplate from "./adaptiveCards/notification-default.json"; -import { CardData } from "./cardModels"; -import { notificationApp } from "./internal/initialize"; - -// An Azure Function HTTP trigger. -// -// This endpoint is provided by your application to listen to events. You can configure -// your IT processes, other applications, background tasks, etc - to POST events to this -// endpoint. -// -// In response to events, this function sends Adaptive Cards to Teams. You can update the logic in this function -// to suit your needs. You can enrich the event with additional data and send an Adaptive Card as required. -// -// You can add authentication / authorization for this API. Refer to -// https://aka.ms/teamsfx-notification for more details. -const httpTrigger: AzureFunction = async function ( - context: Context, - req: HttpRequest -): Promise { - // By default this function will iterate all the installation points and send an Adaptive Card - // to every installation. - for (const target of await notificationApp.notification.installations()) { - await target.sendAdaptiveCard( - new ACData.Template(notificationTemplate).expand({ - $root:{ - title: "New Event Occurred!", - appName: "Contoso App Notification", - description: `This is a sample http-triggered notification to ${target.type}`, - notificationUrl: "https://aka.ms/teamsfx-notification-new", - } - }) - ); - - // Note - you can filter the installations if you don't want to send the event to every installation. - - /** For example, if the current target is a "Group" this means that the notification application is - * installed in a Group Chat. - if (target.type === NotificationTargetType.Group) { - // You can send the Adaptive Card to the Group Chat - await target.sendAdaptiveCard(...); - - // Or you can list all members in the Group Chat and send the Adaptive Card to each Team member - const members = await target.members(); - for (const member of members) { - // You can even filter the members and only send the Adaptive Card to members that fit a criteria - await member.sendAdaptiveCard(...); - } - } - **/ - - /** If the current target is "Channel" this means that the notification application is installed - * in a Team. - if (target.type === NotificationTargetType.Channel) { - // If you send an Adaptive Card to the Team (the target), it sends it to the `General` channel of the Team - await target.sendAdaptiveCard(...); - - // Alternatively, you can list all channels in the Team and send the Adaptive Card to each channel - const channels = await target.channels(); - for (const channel of channels) { - await channel.sendAdaptiveCard(...); - } - - // Or, you can list all members in the Team and send the Adaptive Card to each Team member - const members = await target.members(); - for (const member of members) { - await member.sendAdaptiveCard(...); - } - } - **/ - - /** If the current target is "Person" this means that the notification application is installed in a - * personal chat. - if (target.type === NotificationTargetType.Person) { - // Directly notify the individual person - await target.sendAdaptiveCard(...); - } - **/ - } - - /** You can also find someone and notify the individual person - const member = await notificationApp.notification.findMember( - async (m) => m.account.email === "someone@contoso.com" - ); - await member?.sendAdaptiveCard(...); - **/ - - /** Or find multiple people and notify them - const members = await notificationApp.notification.findAllMembers( - async (m) => m.account.email?.startsWith("test") - ); - for (const member of members) { - await member.sendAdaptiveCard(...); - } - **/ - - context.res = {}; -}; - -export default httpTrigger; diff --git a/large-scale-notification/src/internal/initialize.ts b/large-scale-notification/src/internal/initialize.ts index e398172e2..3ec95d5ea 100644 --- a/large-scale-notification/src/internal/initialize.ts +++ b/large-scale-notification/src/internal/initialize.ts @@ -1,9 +1,8 @@ import { BotBuilderCloudAdapter } from "@microsoft/teamsfx"; import { - storageAccount, - storageAccountURL, - storageAccountKey, + managedIdentityId, + storageAccountName, storageTableName, } from "../consts"; import config from "./config"; @@ -19,9 +18,8 @@ export const notificationApp = new ConversationBot({ notification: { enabled: true, store: new TableStore( - storageAccount, - storageAccountURL, - storageAccountKey, + managedIdentityId, + `https:${storageAccountName}.table.core.windows.net`, storageTableName ), }, diff --git a/large-scale-notification/src/internal/messageHandler.ts b/large-scale-notification/src/internal/messageHandler.ts deleted file mode 100644 index a59deb7c2..000000000 --- a/large-scale-notification/src/internal/messageHandler.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AzureFunction, Context, HttpRequest } from "@azure/functions"; -import { notificationApp } from "./initialize"; -import { ResponseWrapper } from "./responseWrapper"; - -const httpTrigger: AzureFunction = async function ( - context: Context, - req: HttpRequest -): Promise { - const res = new ResponseWrapper(context.res); - await notificationApp.requestHandler(req, res); - return res.body; -}; - -export default httpTrigger; diff --git a/large-scale-notification/src/internal/responseWrapper.ts b/large-scale-notification/src/internal/responseWrapper.ts deleted file mode 100644 index 32bd1f3b2..000000000 --- a/large-scale-notification/src/internal/responseWrapper.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Response } from "botbuilder"; - -// A wrapper to convert Azure Functions Response to Bot Builder's Response. -export class ResponseWrapper implements Response { - socket: any; - originalResponse?: any; - headers?: any; - body?: any; - - constructor(functionResponse?: { [key: string]: any }) { - this.socket = undefined; - this.originalResponse = functionResponse; - } - - end(...args: any[]) { - // do nothing since res.end() is deprecated in Azure Functions. - } - - header(name: string, value: any) { - this.headers[name] = value; - } - - send(body: any) { - // record the body to be returned later. - this.body = body; - this.originalResponse.body = body; - } - - status(status: number) { - // call Azure Functions' res.status(). - return this.originalResponse?.status(status); - } -} diff --git a/large-scale-notification/src/internal/tableStore.ts b/large-scale-notification/src/internal/tableStore.ts index d24ce1fd4..12f7fe59c 100644 --- a/large-scale-notification/src/internal/tableStore.ts +++ b/large-scale-notification/src/internal/tableStore.ts @@ -1,6 +1,6 @@ import { ConversationReference } from "botbuilder"; - -import { AzureNamedKeyCredential, TableClient } from "@azure/data-tables"; +import { DefaultAzureCredential } from "@azure/identity"; +import { TableClient } from "@azure/data-tables"; import { ConversationReferenceStore, ConversationReferenceStoreAddOptions, @@ -16,20 +16,18 @@ export class TableStore implements ConversationReferenceStore { private readonly client: TableClient; constructor( - storageAccountName: string, + managedIdentityId: string, storageAccountURL: string, - storageAccountKey: string, storageTableName: string ) { - const sharedKeyCredential = new AzureNamedKeyCredential( - storageAccountName, - storageAccountKey - ); + const credential = new DefaultAzureCredential({ + managedIdentityClientId: managedIdentityId, + }); this.client = new TableClient( `${storageAccountURL}`, `${storageTableName}`, - sharedKeyCredential, + credential, { allowInsecureConnection: true } ); } diff --git a/large-scale-notification/teamsapp.local.yml b/large-scale-notification/teamsapp.local.yml index 75fac4dff..e1d184bf9 100644 --- a/large-scale-notification/teamsapp.local.yml +++ b/large-scale-notification/teamsapp.local.yml @@ -14,7 +14,7 @@ provision: name: large-scale-notifi${{APP_NAME_SUFFIX}} # Write the information of created resources into environment file for # the specified environment variable(s). - writeToEnvironmentFile: + writeToEnvironmentFile: teamsAppId: TEAMS_APP_ID # Create or reuse an existing Azure Active Directory application for bot. @@ -26,7 +26,7 @@ provision: # The Azure Active Directory application's client id created for bot. botId: BOT_ID # The Azure Active Directory application's client secret created for bot. - botPassword: SECRET_BOT_PASSWORD + botPassword: SECRET_BOT_PASSWORD # Create or update the bot registration on dev.botframework.com - uses: botFramework/create @@ -81,32 +81,3 @@ deploy: - uses: cli/runNpmCommand with: args: install --no-audit - - - uses: file/createOrUpdateEnvironmentFile - with: - target: ./env/.env.${{TEAMSFX_ENV}} - envs: - STORAGE_ACCOUNT_NAME: devstoreaccount1 - STORAGE_ACCOUNT_URL: http://127.0.0.1:10002/devstoreaccount1 - INSTALLATION_TABLE_NAME: localtable - - - uses: file/createOrUpdateEnvironmentFile - with: - target: ./env/.env.${{TEAMSFX_ENV}}.user - envs: - SECRET_STORAGE_ACCOUNT_KEY: ${{SECRET_STORAGE_ACCOUNT_KEY}} - - - # Generate runtime environment variables - - uses: file/createOrUpdateEnvironmentFile - with: - target: ./.localConfigs - envs: - BOT_ID: ${{BOT_ID}} - BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} - BOT_TYPE: 'MultiTenant' - SERVICE_BUS_QUEUE_NAME: ${{SERVICE_BUS_QUEUE_NAME}} - STORAGE_ACCOUNT_NAME: devstoreaccount1 - STORAGE_ACCOUNT_URL: http://127.0.0.1:10002/devstoreaccount1 - INSTALLATION_TABLE_NAME: localtable - STORAGE_ACCOUNT_KEY: ${{SECRET_STORAGE_ACCOUNT_KEY}} diff --git a/large-scale-notification/teamsapp.yml b/large-scale-notification/teamsapp.yml index aed4b8fc4..2cdc07edd 100644 --- a/large-scale-notification/teamsapp.yml +++ b/large-scale-notification/teamsapp.yml @@ -40,7 +40,7 @@ provision: # variable before ARM deployment. parameters: ./infra/azure.parameters.json # Required when deploying ARM template - deploymentName: Create-resources-for-tab + deploymentName: Create-resources-for-sample # Teams Toolkit will download this bicep CLI version from github for you, # will use bicep CLI in PATH if you remove this config. bicepCliVersion: v0.9.1 diff --git a/large-scale-notification/waitSendingFinish/function.json b/large-scale-notification/waitSendingFinish/function.json deleted file mode 100644 index 75fbbb5ca..000000000 --- a/large-scale-notification/waitSendingFinish/function.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "bindings": [ - { - "name": "input", - "type": "activityTrigger", - "direction": "in" - } - ], - "scriptFile": "../dist/src/functions/waitSendingFinishActivity.js" -} \ No newline at end of file diff --git a/share-now/.vscode/tasks.json b/share-now/.vscode/tasks.json index 9a19a1816..4f913c5e9 100644 --- a/share-now/.vscode/tasks.json +++ b/share-now/.vscode/tasks.json @@ -181,7 +181,7 @@ "background": { "activeOnStart": true, "beginsPattern": "[nodemon] starting", - "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" + "endsPattern": "app listening to|Bot/ME service listening at|[nodemon] app crashed" } }, "presentation": { diff --git a/share-now/api/package.json b/share-now/api/package.json index 192229504..43753a79e 100644 --- a/share-now/api/package.json +++ b/share-now/api/package.json @@ -2,7 +2,7 @@ "name": "teamsfx-sample-share-now-function", "version": "1.0.0", "engines": { - "node": "16 || 18" + "node": "18 || 20" }, "scripts": { "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev", @@ -16,7 +16,7 @@ }, "dependencies": { "@azure/functions": "^4.2.0", - "@microsoft/teamsfx": "^2.2.0", + "@microsoft/teamsfx": "^3.0.0-alpha", "@microsoft/microsoft-graph-client": "^3.0.1", "isomorphic-fetch": "^3.0.0", "tedious": "^9.2.3" diff --git a/share-now/api/src/utils/common.ts b/share-now/api/src/utils/common.ts index 53923e6bc..03609f04a 100644 --- a/share-now/api/src/utils/common.ts +++ b/share-now/api/src/utils/common.ts @@ -1,11 +1,22 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import "isomorphic-fetch"; -import { TeamsFx, getTediousConnectionConfig } from "@microsoft/teamsfx"; import * as tedious from "tedious"; export async function getSQLConnection() { - const teamsfx = new TeamsFx(); - const config = await getTediousConnectionConfig(teamsfx); + const config = { + server: process.env.SQL_ENDPOINT, + authentication: { + type: "default", + options: { + userName: process.env.SQL_USER_NAME, + password: process.env.SQL_PASSWORD, + }, + }, + options: { + database: process.env.SQL_DATABASE_NAME, + encrypt: true, + }, + }; const connection = new tedious.Connection(config); return new Promise((resolve, reject) => { connection.on('connect', err => { diff --git a/share-now/bot/index.ts b/share-now/bot/index.ts index 07a471540..7e84bcb35 100644 --- a/share-now/bot/index.ts +++ b/share-now/bot/index.ts @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import * as restify from 'restify'; +import express from "express"; // Import required bot services. // See https://aka.ms/bot-services to learn more about the different parts of a bot. @@ -56,15 +56,16 @@ adapter.onTurnError = onTurnErrorHandler; // Create the main dialog. const messageExtensionBot = new MessageExtensionBot(); -// Create HTTP server. -const server = restify.createServer(); -server.use(restify.plugins.bodyParser()); -server.listen(process.env.port || process.env.PORT || 3978, () => { - console.log(`\n${server.name} listening to ${server.url}`); +// Create express application. +const expressApp = express(); +expressApp.use(express.json()); + +const server = expressApp.listen(process.env.port || process.env.PORT || 3978, () => { + console.log(`\nBot Started, ${expressApp.name} listening to`, server.address()); }); // Listen for incoming requests. -server.post('/api/messages', (req, res, next) => { +expressApp.post('/api/messages', (req, res, next) => { adapter.process(req, res, async (context) => { // Route to main dialog. await messageExtensionBot.run(context); diff --git a/share-now/bot/package.json b/share-now/bot/package.json index 3e5507076..378312dd8 100644 --- a/share-now/bot/package.json +++ b/share-now/bot/package.json @@ -6,7 +6,7 @@ "license": "MIT", "main": "./lib/index.js", "engines": { - "node": "16 || 18" + "node": "18 || 20" }, "scripts": { "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev", @@ -21,20 +21,21 @@ "url": "https://github.com/OfficeDev/TeamsFx-Samples.git" }, "dependencies": { - "@microsoft/teamsfx": "^2.0.0", + "@microsoft/teamsfx": "^3.0.0-alpha", "adaptivecards": "^2.9.0", "axios": "^0.21.1", - "botbuilder": "^4.18.0", + "botbuilder": "^4.23.1", "replace": "^1.2.0", - "restify": "^10.0.0", + "express": "^5.0.1", "tedious": "^9.2.3" }, "devDependencies": { + "@types/express": "^5.0.0", "@types/node": "^22.7.5", "@types/restify": "^8.5.5", "@types/tedious": "^4.0.3", "env-cmd": "^10.1.0", - "nodemon": "^2.0.7", + "nodemon": "^3.1.7", "ts-node": "^10.4.0", "typescript": "^5.3.3" } diff --git a/share-now/bot/utils/common.ts b/share-now/bot/utils/common.ts index f8f41e05c..e11e4de05 100644 --- a/share-now/bot/utils/common.ts +++ b/share-now/bot/utils/common.ts @@ -1,10 +1,21 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { getTediousConnectionConfig, TeamsFx } from "@microsoft/teamsfx"; import * as tedious from "tedious"; export async function getSQLConnection() { - const teamsfx = new TeamsFx(); - const config = await getTediousConnectionConfig(teamsfx); + const config = { + server: process.env.SQL_ENDPOINT, + authentication: { + type: "default", + options: { + userName: process.env.SQL_USER_NAME, + password: process.env.SQL_PASSWORD, + }, + }, + options: { + database: process.env.SQL_DATABASE_NAME, + encrypt: true, + }, + }; const connection = new tedious.Connection(config); return new Promise((resolve, reject) => { connection.on('connect', err => { @@ -25,7 +36,7 @@ export async function executeQuery(query, connection): Promise { const request = new tedious.Request(query, (err) => { if (err) { console.log(err); - reject(err) + reject(err); } }); request.on("row", (columns) => { diff --git a/team-central-dashboard/vite.config.ts b/team-central-dashboard/vite.config.ts index 312e17d1a..bf2d61647 100644 --- a/team-central-dashboard/vite.config.ts +++ b/team-central-dashboard/vite.config.ts @@ -4,9 +4,11 @@ import fs from "fs"; export default defineConfig({ plugins: [react()], + define: { + global: 'globalThis', + }, server: { port: 53000, - // if you are using SSL https: { cert: process.env.SSL_CRT_FILE ? fs.readFileSync(process.env.SSL_CRT_FILE) : undefined, key: process.env.SSL_KEY_FILE ? fs.readFileSync(process.env.SSL_KEY_FILE) : undefined,