diff --git a/NPM-search-connector-M365/images/npm-search-connector-M365.gif b/NPM-search-connector-M365/images/npm-search-connector-M365.gif index 29fe97be..1209bf97 100644 Binary files a/NPM-search-connector-M365/images/npm-search-connector-M365.gif and b/NPM-search-connector-M365/images/npm-search-connector-M365.gif differ diff --git a/adaptive-card-notification/assets/sampleDemo.gif b/adaptive-card-notification/assets/sampleDemo.gif index 8a84b69e..39c5f5d2 100644 Binary files a/adaptive-card-notification/assets/sampleDemo.gif and b/adaptive-card-notification/assets/sampleDemo.gif differ diff --git a/bot-sso/README.md b/bot-sso/README.md index 928ef3aa..131ee44b 100644 --- a/bot-sso/README.md +++ b/bot-sso/README.md @@ -17,7 +17,7 @@ A bot, chatbot, or conversational bot is an app that responds to simple commands This is a sample chatbot application demonstrating Single Sign-on using `botbuilder` and Teams Framework that can respond to a `show` message. -![Bot SSO Overview](images/bot-sso.gif) +![Bot SSO Overview](assets/sampleDemo.gif) ## This sample illustrates - Use Teams Toolkit to create a Teams bot app. @@ -68,6 +68,7 @@ This is a sample chatbot application demonstrating Single Sign-on using `botbuil |---|---|---| |Apr 19, 2022| IvanJobs | update to support Teams Toolkit v4.0.0| |Dec 7, 2022| yukun-dong | update to support Teams Toolkit v5.0.0| +|Feb 22, 2024| yukun-dong | update card to adaptive card| ## Feedback We really appreciate your feedback! If you encounter any issue or error, please report issues to us following the [Supporting Guide](https://github.com/OfficeDev/TeamsFx-Samples/blob/dev/SUPPORT.md). Meanwhile you can make [recording](https://aka.ms/teamsfx-record) of your journey with our product, they really make the product better. Thank you! \ No newline at end of file diff --git a/bot-sso/assets/sampleDemo.gif b/bot-sso/assets/sampleDemo.gif index beefde75..e9ace2e5 100644 Binary files a/bot-sso/assets/sampleDemo.gif and b/bot-sso/assets/sampleDemo.gif differ diff --git a/bot-sso/assets/thumbnail.png b/bot-sso/assets/thumbnail.png index 5c58cc09..e6672e35 100644 Binary files a/bot-sso/assets/thumbnail.png and b/bot-sso/assets/thumbnail.png differ diff --git a/bot-sso/commands/showUserProfile.ts b/bot-sso/commands/showUserProfile.ts index 2cf78923..7324c9cb 100644 --- a/bot-sso/commands/showUserProfile.ts +++ b/bot-sso/commands/showUserProfile.ts @@ -49,10 +49,25 @@ export class ShowUserProfile implements SSOCommand { const buffer = Buffer.from(photoBinary); const imageUri = "data:image/png;base64," + buffer.toString("base64"); - const card = CardFactory.thumbnailCard( - "User Picture", - CardFactory.images([imageUri]) - ); + const card = CardFactory.adaptiveCard({ + type: "AdaptiveCard", + body: [ + { + type: "TextBlock", + text: "User Picture", + weight: "Bolder", + size: "Medium" + }, + { + type: "Image", + url: imageUri, + size: "Large", + horizontalAlignment: "Left" + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.4" + }); await context.sendActivity({ attachments: [card] }); } else { await context.sendActivity( diff --git a/bot-sso/images/bot-sso.gif b/bot-sso/images/bot-sso.gif deleted file mode 100644 index 3bb1ffce..00000000 Binary files a/bot-sso/images/bot-sso.gif and /dev/null differ diff --git a/developer-assist-dashboard/README.md b/developer-assist-dashboard/README.md index 9a29df48..1a5fca3d 100644 --- a/developer-assist-dashboard/README.md +++ b/developer-assist-dashboard/README.md @@ -17,7 +17,7 @@ Microsoft Teams supports the ability to run web-based UI inside "custom tabs" th Developer Assist Dashboard shows you how to build a tab with Azure DevOps work items, GitHub issues and Planner tasks to accelerate developer team collaboration and productivity. Developer Assist Dashboard is capable of working on Microsoft Teams, Outlook Web and Microsoft 365 app. - + > Note: This sample will only provision [single tenant](https://learn.microsoft.com/azure/active-directory/develop/single-and-multi-tenant-apps#who-can-sign-in-to-your-app) Azure Active Directory app. For multi-tenant support, please refer to this [wiki](https://aka.ms/teamsfx-multi-tenant). diff --git a/developer-assist-dashboard/images/preview.png b/developer-assist-dashboard/images/preview.png index cbc0b968..352f8ff9 100644 Binary files a/developer-assist-dashboard/images/preview.png and b/developer-assist-dashboard/images/preview.png differ diff --git a/graph-connector-app/assets/sampleDemo.gif b/graph-connector-app/assets/sampleDemo.gif index d3646faf..438a607e 100644 Binary files a/graph-connector-app/assets/sampleDemo.gif and b/graph-connector-app/assets/sampleDemo.gif differ diff --git a/graph-connector-app/assets/thumbnail.png b/graph-connector-app/assets/thumbnail.png index 6a3b1433..54345f14 100644 Binary files a/graph-connector-app/assets/thumbnail.png and b/graph-connector-app/assets/thumbnail.png differ diff --git a/graph-connector-app/images/ingest.png b/graph-connector-app/images/ingest.png index 3325f842..661e59ec 100644 Binary files a/graph-connector-app/images/ingest.png and b/graph-connector-app/images/ingest.png differ diff --git a/graph-connector-app/images/query.png b/graph-connector-app/images/query.png index ed241ded..0a932192 100644 Binary files a/graph-connector-app/images/query.png and b/graph-connector-app/images/query.png differ diff --git a/graph-connector-app/images/start.png b/graph-connector-app/images/start.png index c3c23230..07ddb355 100644 Binary files a/graph-connector-app/images/start.png and b/graph-connector-app/images/start.png differ diff --git a/graph-connector-bot/assets/sampleDemo.gif b/graph-connector-bot/assets/sampleDemo.gif index affedb9d..13afc031 100644 Binary files a/graph-connector-bot/assets/sampleDemo.gif and b/graph-connector-bot/assets/sampleDemo.gif differ diff --git a/graph-connector-bot/assets/thumbnail.png b/graph-connector-bot/assets/thumbnail.png index 7773f098..9b8c1f12 100644 Binary files a/graph-connector-bot/assets/thumbnail.png and b/graph-connector-bot/assets/thumbnail.png differ diff --git a/graph-toolkit-contact-exporter/assets/sampleDemo.gif b/graph-toolkit-contact-exporter/assets/sampleDemo.gif index 6b77e25a..836e25ea 100644 Binary files a/graph-toolkit-contact-exporter/assets/sampleDemo.gif and b/graph-toolkit-contact-exporter/assets/sampleDemo.gif differ diff --git a/graph-toolkit-contact-exporter/assets/thumbnail.png b/graph-toolkit-contact-exporter/assets/thumbnail.png index 10d9eb37..0933ff80 100644 Binary files a/graph-toolkit-contact-exporter/assets/thumbnail.png and b/graph-toolkit-contact-exporter/assets/thumbnail.png differ diff --git a/graph-toolkit-contact-exporter/images/card.png b/graph-toolkit-contact-exporter/images/card.png index 2a50e5ff..0331299a 100644 Binary files a/graph-toolkit-contact-exporter/images/card.png and b/graph-toolkit-contact-exporter/images/card.png differ diff --git a/graph-toolkit-contact-exporter/images/select.png b/graph-toolkit-contact-exporter/images/select.png index fd2f1b09..13f008c9 100644 Binary files a/graph-toolkit-contact-exporter/images/select.png and b/graph-toolkit-contact-exporter/images/select.png differ diff --git a/graph-toolkit-contact-exporter/images/start.png b/graph-toolkit-contact-exporter/images/start.png index 05922430..f5bf6128 100644 Binary files a/graph-toolkit-contact-exporter/images/start.png and b/graph-toolkit-contact-exporter/images/start.png differ diff --git a/graph-toolkit-one-productivity-hub/assets/sampleDemo.gif b/graph-toolkit-one-productivity-hub/assets/sampleDemo.gif index 39caa0e1..0c885dfd 100644 Binary files a/graph-toolkit-one-productivity-hub/assets/sampleDemo.gif and b/graph-toolkit-one-productivity-hub/assets/sampleDemo.gif differ diff --git a/graph-toolkit-one-productivity-hub/assets/thumbnail.png b/graph-toolkit-one-productivity-hub/assets/thumbnail.png index cd43ea4d..87fb541a 100644 Binary files a/graph-toolkit-one-productivity-hub/assets/thumbnail.png and b/graph-toolkit-one-productivity-hub/assets/thumbnail.png differ diff --git a/graph-toolkit-one-productivity-hub/images/oneproductivityhub.png b/graph-toolkit-one-productivity-hub/images/oneproductivityhub.png index faf84fb4..7699b5a0 100644 Binary files a/graph-toolkit-one-productivity-hub/images/oneproductivityhub.png and b/graph-toolkit-one-productivity-hub/images/oneproductivityhub.png differ diff --git a/graph-toolkit-one-productivity-hub/images/start.png b/graph-toolkit-one-productivity-hub/images/start.png index 03b64db1..a509174b 100644 Binary files a/graph-toolkit-one-productivity-hub/images/start.png and b/graph-toolkit-one-productivity-hub/images/start.png differ diff --git a/hello-world-in-meeting/images/sidepanel.png b/hello-world-in-meeting/images/sidepanel.png index 37a6026d..17be14c9 100644 Binary files a/hello-world-in-meeting/images/sidepanel.png and b/hello-world-in-meeting/images/sidepanel.png differ diff --git a/hello-world-in-meeting/images/thumbnail.png b/hello-world-in-meeting/images/thumbnail.png index 6155a0ab..140558ad 100644 Binary files a/hello-world-in-meeting/images/thumbnail.png and b/hello-world-in-meeting/images/thumbnail.png differ diff --git a/hello-world-tab-docker/.dockerignore b/hello-world-tab-docker/.dockerignore new file mode 100644 index 00000000..c0d9d32e --- /dev/null +++ b/hello-world-tab-docker/.dockerignore @@ -0,0 +1,12 @@ +appPackage +assets +build +env +infra +node_modules +.gitignore +aad.manifest.json +*.md +compose*.yaml +Dockerfile +teamsapp*.yml diff --git a/hello-world-tab-docker/.gitignore b/hello-world-tab-docker/.gitignore new file mode 100644 index 00000000..4f392ca6 --- /dev/null +++ b/hello-world-tab-docker/.gitignore @@ -0,0 +1,18 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# TeamsFx files +env/.env.*.user +env/.env.local +.DS_Store +build +appPackage/build +.deployment +.localConfigs + +# dependencies +/node_modules + +# testing +/coverage + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/hello-world-tab-docker/.vscode/extensions.json b/hello-world-tab-docker/.vscode/extensions.json new file mode 100644 index 00000000..aac0a6e3 --- /dev/null +++ b/hello-world-tab-docker/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension" + ] +} diff --git a/hello-world-tab-docker/.vscode/launch.json b/hello-world-tab-docker/.vscode/launch.json new file mode 100644 index 00000000..29fbf9a9 --- /dev/null +++ b/hello-world-tab-docker/.vscode/launch.json @@ -0,0 +1,72 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Remote in Teams (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "group 1: Teams", + "order": 3 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote in Teams (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "group 1: Teams", + "order": 3 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Attach to Frontend in Teams (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "all", + "hidden": true + }, + "postDebugTask": "Stop Teams App in Docker", + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Attach to Frontend in Teams (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Teams (Edge)", + "configurations": ["Attach to Frontend in Teams (Edge)"], + "preLaunchTask": "Start Teams App in Docker", + "presentation": { + "group": "group 1: Teams", + "order": 1 + }, + "stopAll": true + }, + { + "name": "Debug in Teams (Chrome)", + "configurations": ["Attach to Frontend in Teams (Chrome)"], + "preLaunchTask": "Start Teams App in Docker", + "presentation": { + "group": "group 1: Teams", + "order": 2 + }, + "stopAll": true + } + ] +} diff --git a/hello-world-tab-docker/.vscode/settings.json b/hello-world-tab-docker/.vscode/settings.json new file mode 100644 index 00000000..a2833c70 --- /dev/null +++ b/hello-world-tab-docker/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "debug.onTaskErrors": "abort", + "json.schemas": [ + { + "fileMatch": [ + "/aad.*.json" + ], + "schema": {} + } + ], + "azureFunctions.stopFuncTaskPostDebug": false, + "azureFunctions.showProjectWarning": false, + "csharp.suppressDotnetRestoreNotification": true +} diff --git a/hello-world-tab-docker/.vscode/tasks.json b/hello-world-tab-docker/.vscode/tasks.json new file mode 100644 index 00000000..76ac35d5 --- /dev/null +++ b/hello-world-tab-docker/.vscode/tasks.json @@ -0,0 +1,60 @@ +// 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": "Validate Docker prerequisites", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "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": [ + 53000, // tab service port + 7071, // backend service port + 9229 // backend inspector port for Node.js debugger + ] + } + }, + { + "label": "Start Teams App in Docker", + "dependsOn": [ + "Validate Docker prerequisites", + "Provision", + "docker-compose-up" + ], + "dependsOrder": "sequence" + }, + { + // 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" + } + }, + { + "label": "Stop Teams App in Docker", + "type": "shell", + "command": "docker compose -f ${workspaceFolder}/compose-debug.yml down", + "isBackground": true + }, + { + "type": "shell", + "label": "docker-compose-up", + "command": "docker compose -f ${workspaceFolder}/compose-debug.yml up -d", + "isBackground": true, + "options": { + "env": { + "HOME": "${userHome}" + } + } + } + ] +} diff --git a/hello-world-tab-docker/Dockerfile b/hello-world-tab-docker/Dockerfile new file mode 100644 index 00000000..575492d0 --- /dev/null +++ b/hello-world-tab-docker/Dockerfile @@ -0,0 +1,14 @@ +FROM node:18.18.0 AS development + +WORKDIR /app +COPY package*.json /app/ +RUN npm install +COPY . /app/ + +CMD ["npm", "start"] + +FROM development AS build +RUN npm run build + +FROM nginx:1.21.3-alpine AS production +COPY --from=build /app/build /usr/share/nginx/html diff --git a/hello-world-tab-docker/README.md b/hello-world-tab-docker/README.md new file mode 100644 index 00000000..f9d8172f --- /dev/null +++ b/hello-world-tab-docker/README.md @@ -0,0 +1,83 @@ +--- +page_type: sample +languages: +- typescript +products: +- office-teams +- office +name: Tab App with Azure Backend +urlFragment: officedev-teamsfx-samples-tab-hello-world-tab-with-backend +description: A Hello World app of Microsoft Teams Tab app which has a backend service. +extensions: + createdDate: "2021-11-30" +--- +# Getting Started with Hello World Tab with Backend Sample (Azure) + +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. + +Hello World Tab with Backend shows you how to build a tab app with an Azure Function as backend, how to get user login information with SSO and how to call Azure Function from frontend tab. + +![Hello World Tab](assets/sampleDemo.gif) + +> Note: This sample will only provision [single tenant](https://learn.microsoft.com/azure/active-directory/develop/single-and-multi-tenant-apps#who-can-sign-in-to-your-app) Azure Active Directory app. For multi-tenant support, please refer to this [wiki](https://aka.ms/teamsfx-multi-tenant). + +## This sample illustrates + +- How to use Teams Toolkit to create a Teams tab app. +- How to use TeamsFx SDK to call Azure Functions. +- How to use TeamsFx SDK in Azure Function to call Graph to get user info. + +## Prerequisites + +- [Node.js](https://nodejs.org/), supported versions: 16, 18 +- A Microsoft 365 account. If you do not have Microsoft 365 account, apply one from [Microsoft 365 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 [TeamsFx CLI](https://aka.ms/teamsfx-cli) + +# Note +- This sample has adopted [On-Behalf-Of Flow](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow) to implement SSO. + +- This sample uses Azure Function as middle-tier service, and make authenticated requests to call Graph from Azure Function. + +- Due to system webview limitations, users in the tenant with conditional access policies applied cannot consent permissions when conduct an OAuth flow within the Teams mobile clients, it would show error: "xxx requires you to secure this device...". + +## Minimal path to awesome + +### Run the app locally + +- From VS Code: + 1. hit `F5` to start debugging. Alternatively open the `Run and Debug Activity` Panel and select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`. + +- From TeamsFx CLI: + 1. Run command: `teamsapp provision --env local` . + 1. Run command: `teamsapp deploy --env local` . + 1. Run command: `teamsapp preview --env local` . + +### Deploy the app to Azure + +- From VS Code: + 1. Sign into Azure by clicking the `Sign in to Azure` under the `ACCOUNTS` section from sidebar. + 1. Click `Provision` from `LIFECYCLE` section or open the command palette and select: `Teams: Provision`. + 1. Click `Deploy` or open the command palette and select: `Teams: Deploy`. + +- From TeamsFx CLI: + 1. Run command: `teamsapp auth login azure`. + 1. Run command: `teamsapp provision --env dev`. + 1. Run command: `teamsapp deploy --env dev`. + +### Preview the app in Teams + +- From VS Code: + 1. Open the `Run and Debug Activity` Panel. Select `Launch Remote (Edge)` or `Launch Remote (Chrome)` from the launch configuration drop-down. + +- From TeamsFx CLI: + 1. Run command: `teamsapp preview --env dev`. + +## Version History + +|Date| Author| Comments| +|---|---|---| +|May 18, 2022| hund030 | update to support Teams Toolkit v4.0.0| +|Dec 8, 2022| hund030 | update to support Teams Toolkit v5.0.0| + +## Feedback +We really appreciate your feedback! If you encounter any issue or error, please report issues to us following the [Supporting Guide](https://github.com/OfficeDev/TeamsFx-Samples/blob/dev/SUPPORT.md). Meanwhile you can make [recording](https://aka.ms/teamsfx-record) of your journey with our product, they really make the product better. Thank you! \ No newline at end of file diff --git a/hello-world-tab-docker/aad.manifest.json b/hello-world-tab-docker/aad.manifest.json new file mode 100644 index 00000000..b9a875d9 --- /dev/null +++ b/hello-world-tab-docker/aad.manifest.json @@ -0,0 +1,109 @@ +{ + "id": "${{AAD_APP_OBJECT_ID}}", + "appId": "${{AAD_APP_CLIENT_ID}}", + "name": "hello-world-tab-with-backend-aad", + "accessTokenAcceptedVersion": 2, + "signInAudience": "AzureADMyOrg", + "optionalClaims": { + "idToken": [], + "accessToken": [ + { + "name": "idtyp", + "source": null, + "essential": false, + "additionalProperties": [] + } + ], + "saml2Token": [] + }, + "requiredResourceAccess": [ + { + "resourceAppId": "Microsoft Graph", + "resourceAccess": [ + { + "id": "User.Read", + "type": "Scope" + } + ] + } + ], + "oauth2Permissions": [ + { + "adminConsentDescription": "Allows Teams to call the app's web APIs as the current user.", + "adminConsentDisplayName": "Teams can access app's web APIs", + "id": "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}", + "isEnabled": true, + "type": "User", + "userConsentDescription": "Enable Teams to call this app's web APIs with the same rights that you have", + "userConsentDisplayName": "Teams can access app's web APIs and make requests on your behalf", + "value": "access_as_user" + } + ], + "preAuthorizedApplications": [ + { + "appId": "1fec8e78-bce4-4aaf-ab1b-5451cc387264", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "5e3ce6c0-2b1f-4285-8d4b-75ee78787346", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "d3590ed6-52b3-4102-aeff-aad2292ab01c", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "00000002-0000-0ff1-ce00-000000000000", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "bc59ab01-8403-45c6-8796-ac3ef710b3e3", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "0ec893e0-5785-4de6-99da-4ed124e5296c", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "4765445b-32c6-49b0-83e6-1d93765276ca", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "4345a7b9-9a63-4910-a426-35363201d503", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + } + ], + "identifierUris": [ + "api://${{TAB_DOMAIN}}/${{AAD_APP_CLIENT_ID}}" + ], + "replyUrlsWithType": [ + { + "url": "${{TAB_ENDPOINT}}/auth-end.html", + "type": "Web" + }, + { + "url": "${{TAB_ENDPOINT}}/auth-end.html?clientId=${{AAD_APP_CLIENT_ID}}", + "type": "Spa" + }, + { + "url": "${{TAB_ENDPOINT}}/blank-auth-end.html", + "type": "Spa" + } + ] +} \ No newline at end of file diff --git a/hello-world-tab-docker/api/.dockerignore b/hello-world-tab-docker/api/.dockerignore new file mode 100644 index 00000000..fd53816b --- /dev/null +++ b/hello-world-tab-docker/api/.dockerignore @@ -0,0 +1,7 @@ +bin +dist +node_modules +.gitignore +.localConfigs +Dockerfile +*.md \ No newline at end of file diff --git a/hello-world-tab-docker/api/.funcignore b/hello-world-tab-docker/api/.funcignore new file mode 100644 index 00000000..e470fa3a --- /dev/null +++ b/hello-world-tab-docker/api/.funcignore @@ -0,0 +1,12 @@ +*.js.map +*.ts +.git* +.localConfigs +.vscode +local.settings.json +test +tsconfig.json +.DS_Store +.deployment +node_modules/.bin +node_modules/azure-functions-core-tools diff --git a/hello-world-tab-docker/api/.gitignore b/hello-world-tab-docker/api/.gitignore new file mode 100644 index 00000000..67f9bc59 --- /dev/null +++ b/hello-world-tab-docker/api/.gitignore @@ -0,0 +1,18 @@ +# TypeScript output +dist +out + +# Dependency directories +node_modules + +# Azure Functions artifacts +bin +obj +appsettings.json +local.settings.json + +# misc +.DS_Store + +# Local data +.localConfigs diff --git a/hello-world-tab-docker/api/Dockerfile b/hello-world-tab-docker/api/Dockerfile new file mode 100644 index 00000000..3dd78038 --- /dev/null +++ b/hello-world-tab-docker/api/Dockerfile @@ -0,0 +1,15 @@ +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS binding +WORKDIR /binding +COPY extensions.csproj . +RUN dotnet build extensions.csproj -o bin --ignore-failed-sources + +FROM mcr.microsoft.com/azure-functions/node:4-node18 + +ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ + AzureFunctionsJobHost__Logging__Console__IsEnabled=true + +COPY . /home/site/wwwroot +COPY --from=binding /binding /home/site/wwwroot + +RUN cd /home/site/wwwroot && \ + npm install && npm run build diff --git a/hello-world-tab-docker/api/README.md b/hello-world-tab-docker/api/README.md new file mode 100644 index 00000000..dae273aa --- /dev/null +++ b/hello-world-tab-docker/api/README.md @@ -0,0 +1,104 @@ +# Server-side code in Teams applications + +Azure Functions are a great way to add server-side behaviors to any Teams application. + +## Prerequisites + +- [Node.js](https://nodejs.org/), supported versions: 16, 18 +- A Microsoft 365 account. If you do not have Microsoft 365 account, apply one from [Microsoft 365 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 [TeamsFx CLI](https://aka.ms/teamsfx-cli) + +## Develop + +The Teams Toolkit IDE Extension and TeamsFx CLI provide template code for you to get started with Azure Functions for your Teams application. Microsoft Teams Framework simplifies the task of establishing the user's identity within the Azure Function. + +The template handles calls from your Teams "custom tab" (client-side of your app), initializes the TeamsFx SDK to access the current user context, and demonstrates how to obtain a pre-authenticated Microsoft Graph Client. Microsoft Graph is the "data plane" of Microsoft 365 - you can use it to access content within Microsoft 365 in your company. With it you can read and write documents, SharePoint collections, Teams channels, and many other entities within Microsoft 365. Read more about [Microsoft Graph](https://docs.microsoft.com/en-us/graph/overview). + +You can add your logic to the single Azure Function created by this template, as well as add more functions as necessary. See [Azure Functions developer guide](https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference) for more information. + +### Call the Function + +To call your Azure Function, the client sends an HTTP request with an SSO token in the `Authorization` header. The token can be retrieved using the TeamsFx SDK from your app's client (custom tab). Here is an example: + +```ts +import { TeamsFx } from "@microsoft/teamsfx"; + +const teamsfx = new TeamsFx(); +const accessToken = await teamsfx.getCredential().getToken(""); +// note: empty string argument on the previous line is required for now, this will be fixed in a later release +const response = await fetch(`${functionEndpoint}/api/${functionName}`, { + headers: { + Authorization: `Bearer ${accessToken.token}`, + }, +}); +``` + +### Add More Functions + +- From Visual Studio Code, open the command palette, select `Teams: Add Resources` and select `Azure Function App`. +- From TeamsFx CLI: run command `teamsapp resource add azure-function --function-name ` in your project directory. + +## Change Node.js runtime version + +By default, Teams Toolkit and TeamsFx CLI will provision an Azure function app with function runtime version 3, and node runtime version 12. You can change the node version through Azure Portal. + +- Sign in to [Azure Portal](https://azure.microsoft.com/). +- Find your application's resource group and Azure Function app resource. The resource group name and the Azure function app name are stored in your project configuration file `.fx/env.*.json`. You can find them by searching the key `resourceGroupName` and `functionAppName` in that file. +- After enter the home page of the Azure function app, you can find a navigation item called `Configuration` under `settings` group. +- Click `Configuration`, you would see a list of settings. Then click `WEBSITE_NODE_DEFAULT_VERSION` and update the value to `~16` or `~18` according to your requirement. +- After Click `OK` button, don't forget to click `Save` button on the top of the page. + +Then following requests sent to the Azure function app will be handled by new node runtime version. + +## Debug + +- From Visual Studio Code: Start debugging the project by hitting the `F5` key in Visual Studio Code. Alternatively use the `Run and Debug Activity Panel` in Visual Studio Code and click the `Start Debugging` green arrow button. +- From TeamsFx CLI: Start debugging the project by executing the command `teamsapp preview --local` in your project directory. + +## Edit the manifest + +You can find the Teams app manifest in `./appPackage` folder. The folder contains one manifest file: +* `manifest.template.json`: Manifest file for Teams app running locally or running remotely (After deployed to Azure). + +This file contains template arguments with `${{...}}` statements which will be replaced at build time. You may add any extra properties or permissions you require to this file. See the [schema reference](https://docs.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema) for more information. + +## Deploy to Azure + +Deploy your project to Azure by following these steps: + +| From Visual Studio Code | From TeamsFx CLI | +| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|
  • Open Teams Toolkit, and sign into Azure by clicking the `Sign in to Azure` under the `ACCOUNTS` section from sidebar.
  • After you signed in, select a subscription under your account.
  • Open the command palette and select: `Teams: Provision`.
  • Open the command palette and select: `Teams: Deploy`.
|
  • Run command `teamsapp auth login azure`.
  • Run command `teamsapp auth set --subscription `.
  • Run command `teamsapp provision`.
  • Run command `teamsapp deploy`.
| + +> Note: Provisioning and deployment may incur charges to your Azure Subscription. + +## Preview + +Once the provisioning and deployment steps are finished, you can preview your app: + +- From Visual Studio Code + + 1. Open the `Run and Debug Activity Panel`. + 1. Select `Launch Remote (Edge)` or `Launch Remote (Chrome)` from the launch configuration drop-down. + 1. Press the Play (green arrow) button to launch your app - now running remotely from Azure. + +- From TeamsFx CLI: execute `teamsapp preview --remote` in your project directory to launch your application. + +## Validate manifest file + +To check that your manifest file is valid: + +- From Visual Studio Code: open the command palette and select: `Teams: Validate manifest file`. +- From TeamsFx CLI: run command `teamsapp validate` in your project directory. + +## Package + +- From Visual Studio Code: open the command palette and select `Teams: Zip Teams metadata package`. +- Alternatively, from the command line run `teamsapp package` in the project directory. + +## Publish to Teams + +Once deployed, you may want to distribute your application to your organization's internal app store in Teams. Your app will be submitted for admin approval. + +- From Visual Studio Code: open the command palette and select: `Teams: Publish to Teams`. +- From TeamsFx CLI: run command `teamsapp publish` in your project directory. diff --git a/hello-world-tab-docker/api/config.ts b/hello-world-tab-docker/api/config.ts new file mode 100644 index 00000000..d7e47e00 --- /dev/null +++ b/hello-world-tab-docker/api/config.ts @@ -0,0 +1,8 @@ +const config = { + authorityHost: process.env.M365_AUTHORITY_HOST, + tenantId: process.env.M365_TENANT_ID, + clientId: process.env.M365_CLIENT_ID, + clientSecret: process.env.M365_CLIENT_SECRET, +}; + +export default config; diff --git a/hello-world-tab-docker/api/extensions.csproj b/hello-world-tab-docker/api/extensions.csproj new file mode 100644 index 00000000..299e3587 --- /dev/null +++ b/hello-world-tab-docker/api/extensions.csproj @@ -0,0 +1,11 @@ + + + netcoreapp3.1 + + ** + + + + + + \ No newline at end of file diff --git a/hello-world-tab-docker/api/getUserProfile/function.json b/hello-world-tab-docker/api/getUserProfile/function.json new file mode 100644 index 00000000..d9071958 --- /dev/null +++ b/hello-world-tab-docker/api/getUserProfile/function.json @@ -0,0 +1,26 @@ +{ + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "get", + "post" + ], + "route": "getUserProfile" + }, + { + "type": "http", + "direction": "out", + "name": "$return" + }, + { + "direction": "in", + "name": "teamsfxContext", + "type": "TeamsFx" + } + ], + "scriptFile": "../dist/getUserProfile/index.js" +} \ No newline at end of file diff --git a/hello-world-tab-docker/api/getUserProfile/index.ts b/hello-world-tab-docker/api/getUserProfile/index.ts new file mode 100644 index 00000000..b4aabb71 --- /dev/null +++ b/hello-world-tab-docker/api/getUserProfile/index.ts @@ -0,0 +1,213 @@ +/* This code sample provides a starter kit to implement server side logic for your Teams App in TypeScript, + * refer to https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference for complete Azure Functions + * developer guide. + */ + +// Import polyfills for fetch required by msgraph-sdk-javascript. +import "isomorphic-fetch"; +import { Context, HttpRequest } from "@azure/functions"; +import { + OnBehalfOfCredentialAuthConfig, + OnBehalfOfUserCredential, + UserInfo, +} from "@microsoft/teamsfx"; +import { Client } from "@microsoft/microsoft-graph-client"; +import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials"; +import config from "../config"; + +interface Response { + status: number; + body: { [key: string]: any }; +} + +type TeamsfxContext = { [key: string]: any }; + +/** + * This function handles requests from teamsfx client. + * The HTTP request should contain an SSO token queried from Teams in the header. + * Before trigger this function, teamsfx binding would process the SSO token and generate teamsfx configuration. + * + * This function initializes the teamsfx SDK with the configuration and calls these APIs: + * - new OnBehalfOfUserCredential(accessToken, oboAuthConfig) - Construct OnBehalfOfUserCredential instance with the received SSO token and initialized configuration. + * - getUserInfo() - Get the user's information from the received SSO token. + * + * The response contains multiple message blocks constructed into a JSON object, including: + * - An echo of the request body. + * - The display name encoded in the SSO token. + * - Current user's Microsoft 365 profile if the user has consented. + * + * @param {Context} context - The Azure Functions context object. + * @param {HttpRequest} req - The HTTP request. + * @param {teamsfxContext} TeamsfxContext - The context generated by teamsfx binding. + */ +export default async function run( + context: Context, + req: HttpRequest, + teamsfxContext: TeamsfxContext +): Promise { + context.log("HTTP trigger function processed a request."); + + // Initialize response. + const res: Response = { + status: 200, + body: {}, + }; + + // Put an echo into response body. + res.body.receivedHTTPRequestBody = req.body || ""; + + // Prepare access token. + const accessToken: string = teamsfxContext["AccessToken"]; + if (!accessToken) { + return { + status: 400, + body: { + error: "No access token was found in request header.", + }, + }; + } + + const oboAuthConfig: OnBehalfOfCredentialAuthConfig = { + authorityHost: config.authorityHost, + clientId: config.clientId, + tenantId: config.tenantId, + clientSecret: config.clientSecret, + }; + + let oboCredential: OnBehalfOfUserCredential; + try { + oboCredential = new OnBehalfOfUserCredential(accessToken, oboAuthConfig); + } catch (e) { + context.log.error(e); + return { + status: 500, + body: { + error: + "Failed to construct OnBehalfOfUserCredential using your accessToken. " + + "Ensure your function app is configured with the right Azure AD App registration.", + }, + }; + } + + // Query user's information from the access token. + try { + const currentUser: UserInfo = await oboCredential.getUserInfo(); + if (currentUser && currentUser.displayName) { + res.body.userInfoMessage = `User display name is ${currentUser.displayName}.`; + } else { + res.body.userInfoMessage = + "No user information was found in access token."; + } + } catch (e) { + context.log.error(e); + return { + status: 400, + body: { + error: "Access token is invalid.", + }, + }; + } + + // Create a graph client with default scope to access user's Microsoft 365 data after user has consented. + try { + // Create an instance of the TokenCredentialAuthenticationProvider by passing the tokenCredential instance and options to the constructor + const authProvider = new TokenCredentialAuthenticationProvider( + oboCredential, + { + scopes: ["https://graph.microsoft.com/.default"], + } + ); + + // Initialize Graph client instance with authProvider + const graphClient = Client.initWithMiddleware({ + authProvider: authProvider, + }); + + const profile: any = await graphClient.api("/me").get(); + res.body.graphClientMessage = profile; + } catch (e) { + context.log.error(e); + return { + status: 500, + body: { + error: + "Failed to retrieve user profile from Microsoft Graph. The application may not be authorized.", + }, + }; + } + + return res; +} + +// You can replace the codes above from the function body with comment "Query user's information from the access token." to the end +// with the following codes to use application permission to get user profiles. +// Remember to get admin consent of application permission "User.Read.All". +/* +// Query user's information from the access token. + let userName: string; + try { + const currentUser: UserInfo = await teamsfx.getUserInfo(); + console.log(currentUser); + userName = currentUser.preferredUserName; // Will be used in app credential flow + if (currentUser && currentUser.displayName) { + res.body.userInfoMessage = `User display name is ${currentUser.displayName}.`; + } else { + res.body.userInfoMessage = "No user information was found in access token."; + } + } catch (e) { + context.log.error(e); + return { + status: 400, + body: { + error: "Access token is invalid.", + }, + }; + } + + // Use IdentityType.App + client secret to create a teamsfx + const appAuthConfig: AppCredentialAuthConfig = { + clientId: process.env.M365_CLIENT_ID, + clientSecret: process.env.M365_CLIENT_SECRET, + authorityHost: process.env.M365_AUTHORITY_HOST, + tenantId: process.env.M365_TENANT_ID, + }; + try { + const appCredential = new AppCredential(appAuthConfig); + } catch (e) { + context.log.error(e); + return { + status: 500, + body: { + error: + "App credential error:" + + "Failed to construct TeamsFx using your accessToken. " + + "Ensure your function app is configured with the right Azure AD App registration.", + }, + }; + } + + // Create a graph client with default scope to access user's Microsoft 365 data after user has consented. + try { + // Create an instance of the TokenCredentialAuthenticationProvider by passing the tokenCredential instance and options to the constructor + const authProvider = new TokenCredentialAuthenticationProvider(appCredential, { + scopes: ["https://graph.microsoft.com/.default"], + }); + + // Initialize the Graph client + const graphClient = Client.initWithMiddleware({ + authProvider: authProvider, + }); + + const profile: any = await graphClient.api("/users/"+userName).get(); + res.body.graphClientMessage = profile; + } catch (e) { + context.log.error(e); + return { + status: 500, + body: { + error: + "Failed to retrieve user profile from Microsoft Graph. The application may not be authorized.", + }, + }; + } +*/ diff --git a/hello-world-tab-docker/api/host.json b/hello-world-tab-docker/api/host.json new file mode 100644 index 00000000..369b5be8 --- /dev/null +++ b/hello-world-tab-docker/api/host.json @@ -0,0 +1,11 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + } +} diff --git a/hello-world-tab-docker/api/package.json b/hello-world-tab-docker/api/package.json new file mode 100644 index 00000000..6b725f64 --- /dev/null +++ b/hello-world-tab-docker/api/package.json @@ -0,0 +1,26 @@ +{ + "name": "teamsfx-template-api", + "version": "1.0.0", + "engines": { + "node": "16 || 18" + }, + "scripts": { + "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev", + "dev": "func start --typescript --language-worker=\"--inspect=9229\" --port \"7071\" --cors \"*\"", + "build": "tsc", + "watch:teamsfx": "tsc -w", + "prestart": "npm run build", + "start": "npx func start", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "@azure/functions": "^1.2.2", + "@microsoft/teamsfx": "^2.0.0", + "@microsoft/microsoft-graph-client": "^3.0.5", + "isomorphic-fetch": "^3.0.0" + }, + "devDependencies": { + "env-cmd": "^10.1.0", + "typescript": "^4.4.4" + } +} diff --git a/hello-world-tab-docker/api/proxies.json b/hello-world-tab-docker/api/proxies.json new file mode 100644 index 00000000..b385252f --- /dev/null +++ b/hello-world-tab-docker/api/proxies.json @@ -0,0 +1,4 @@ +{ + "$schema": "http://json.schemastore.org/proxies", + "proxies": {} +} diff --git a/hello-world-tab-docker/api/tsconfig.json b/hello-world-tab-docker/api/tsconfig.json new file mode 100644 index 00000000..cca0ca95 --- /dev/null +++ b/hello-world-tab-docker/api/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "dist", + "rootDir": ".", + "sourceMap": true, + "strict": false, + "typeRoots": ["./node_modules/@types"] + } +} diff --git a/hello-world-tab-docker/appPackage/color.png b/hello-world-tab-docker/appPackage/color.png new file mode 100644 index 00000000..f27ccf20 Binary files /dev/null and b/hello-world-tab-docker/appPackage/color.png differ diff --git a/hello-world-tab-docker/appPackage/manifest.json b/hello-world-tab-docker/appPackage/manifest.json new file mode 100644 index 00000000..f8e58f8b --- /dev/null +++ b/hello-world-tab-docker/appPackage/manifest.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", + "manifestVersion": "1.16", + "version": "1.0.0", + "id": "${{TEAMS_APP_ID}}", + "packageName": "com.microsoft.teams.extension", + "developer": { + "name": "Teams App, Inc.", + "websiteUrl": "https://www.example.com", + "privacyUrl": "https://www.example.com", + "termsOfUseUrl": "https://www.example.com" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "tab-with-backend${{APP_NAME_SUFFIX}}", + "full": "Full name for hello-world-tab-with-backend" + }, + "description": { + "short": "Short description of hello-world-tab-with-backend", + "full": "Full description of hello-world-tab-with-backend" + }, + "accentColor": "#FFFFFF", + "bots": [], + "composeExtensions": [], + "configurableTabs": [ + { + "configurationUrl": "${{TAB_ENDPOINT}}/index.html#/config", + "canUpdateConfiguration": true, + "scopes": [ + "team", + "groupchat" + ] + } + ], + "staticTabs": [ + { + "entityId": "index", + "name": "Personal Tab", + "contentUrl": "${{TAB_ENDPOINT}}/index.html#/tab", + "websiteUrl": "${{TAB_ENDPOINT}}/index.html#/tab", + "scopes": [ + "personal" + ] + } + ], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [ + "${{TAB_DOMAIN}}" + ], + "webApplicationInfo": { + "id": "${{AAD_APP_CLIENT_ID}}", + "resource": "api://${{TAB_DOMAIN}}/${{AAD_APP_CLIENT_ID}}" + }, + "showLoadingIndicator": false +} \ No newline at end of file diff --git a/hello-world-tab-docker/appPackage/outline.png b/hello-world-tab-docker/appPackage/outline.png new file mode 100644 index 00000000..e8cb4b6b Binary files /dev/null and b/hello-world-tab-docker/appPackage/outline.png differ diff --git a/hello-world-tab-docker/assets/sample.json b/hello-world-tab-docker/assets/sample.json new file mode 100644 index 00000000..64b37e24 --- /dev/null +++ b/hello-world-tab-docker/assets/sample.json @@ -0,0 +1,65 @@ +[ + { + "name": "officedev-teamsfx-samples-tab-hello-world-tab-with-backend", + "source": "officeDev", + "title": "Tab App with Azure Backend", + "shortDescription": "A Hello World app of Microsoft Teams Tab app which has a backend service.", + "url": "https://github.com/OfficeDev/TeamsFx-Samples/tree/dev/hello-world-tab-with-backend", + "longDescription": [ + "This is a Hello World app of Microsoft Teams Tab app which accomplishes very simple function like single-sign on. You can run this app locally or deploy it to Microsoft Azure. This app has a Tab frontend and a backend service using Azure Function." + ], + "creationDateTime": "2021-11-30", + "updateDateTime": "2023-10-24", + "products": [ + "Teams", + "TeamsToolkit" + ], + "metadata": [ + { + "key": "TEAMS-SAMPLE-SOURCE", + "value": "OfficeDev" + }, + { + "key": "TEAMS-SERVER-LANGUAGE", + "value": "typescript" + }, + { + "key": "TEAMS-FEATURES", + "value": "tab" + } + ], + "thumbnails": [ + { + "type": "image", + "order": 100, + "url": "https://raw.githubusercontent.com/OfficeDev/TeamsFx-Samples/dev/hello-world-tab-with-backend/assets/thumbnail.png", + "alt": "Tab App with Azure Backend" + } + ], + "authors": [ + { + "gitHubAccount": "hund030", + "pictureUrl": "https://avatars.githubusercontent.com/u/26134943?v=4", + "name": "Zhijie Huang" + } + ], + "references": [ + { + "name": "Teams developer documentation", + "url": "https://aka.ms/TeamsPlatformDocs" + }, + { + "name": "Teams developer questions", + "url": "https://aka.ms/TeamsPlatformFeedback" + }, + { + "name": "Teams development videos from Microsoft", + "url": "https://aka.ms/sample-ref-teams-vids-from-microsoft" + }, + { + "name": "Teams development videos from the community", + "url": "https://aka.ms/sample-ref-teams-vids-from-community" + } + ] + } +] \ No newline at end of file diff --git a/hello-world-tab-docker/assets/sampleDemo.gif b/hello-world-tab-docker/assets/sampleDemo.gif new file mode 100644 index 00000000..d1a37070 Binary files /dev/null and b/hello-world-tab-docker/assets/sampleDemo.gif differ diff --git a/hello-world-tab-docker/assets/thumbnail.png b/hello-world-tab-docker/assets/thumbnail.png new file mode 100644 index 00000000..ef5abde2 Binary files /dev/null and b/hello-world-tab-docker/assets/thumbnail.png differ diff --git a/hello-world-tab-docker/cli.md b/hello-world-tab-docker/cli.md new file mode 100644 index 00000000..ebaaf996 --- /dev/null +++ b/hello-world-tab-docker/cli.md @@ -0,0 +1,19 @@ +## Try sample with TeamsFx CLI + +1. Install [Node.js](https://nodejs.org/en/download/) (use the latest v14 LTS release) +1. To install the TeamsFx CLI, use the npm package manager: + ``` + npm install -g @microsoft/teamsapp-cli + ``` +1. Create hello-world-tab project. + ``` + teamsapp new sample hello-world-tab-with-backend --interactive false + ``` +1. Provision the project to Azure. + ``` + teamsapp provision + ``` +1. Deploy. + ``` + teamsapp deploy + ``` \ No newline at end of file diff --git a/hello-world-tab-docker/compose-debug.yml b/hello-world-tab-docker/compose-debug.yml new file mode 100644 index 00000000..74f67a92 --- /dev/null +++ b/hello-world-tab-docker/compose-debug.yml @@ -0,0 +1,37 @@ +version: '3.8' +services: + frontend: + build: + context: . + dockerfile: Dockerfile + target: development + ports: + - 53000:53000 + volumes: + - ./src:/app/src + - ~/.fx/certificate:/app/.fx/certificate + env_file: + - .localConfigs + environment: + - SSL_CRT_FILE=/app/.fx/certificate/localhost.crt + - SSL_KEY_FILE=/app/.fx/certificate/localhost.key + - REACT_APP_FUNC_ENDPOINT=http://localhost:7071 + - WATCHPACK_POLLING=true + api: + build: + context: ./api + dockerfile: Dockerfile + ports: + - 7071:80 + volumes: + - ./api:/home/site/wwwroot + - /home/site/wwwroot/node_modules + - /home/site/wwwroot/bin + - /home/site/wwwroot/dist + env_file: + - api/.localConfigs + environment: + - CORS_ALLOWED_ORIGINS=["https://localhost:53000"] + - CORS_SUPPORT_CREDENTIALS=true + - CONTAINER_NAME=teamsfx-localhost # Enable CORS, but has issue + # - FUNCTIONS_ENABLE_CORS_CONFIGURATION=true # Enable CORS, requires azure-functions-host 4.31.0 or later \ No newline at end of file diff --git a/hello-world-tab-docker/docker-compose.yml b/hello-world-tab-docker/docker-compose.yml new file mode 100644 index 00000000..51ce2e12 --- /dev/null +++ b/hello-world-tab-docker/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3.8' +services: + frontend: + build: + context: . + dockerfile: Dockerfile + target: production + image: $DOCKER_REGISTRY/hello-world-tab-frontend + container_name: hello-world-tab-frontend + environment: + - "REACT_APP_CLIENT_ID=6427811e-d1a1-4ff9-834f-e99d10e551b3" + - "REACT_APP_START_LOGIN_PAGE_URL=https://helloworld650f48.proudcliff-bba63f36.centralus.azurecontainerapps.io/auth-start.html" + - "REACT_APP_FUNC_NAME=getUserProfile" + - "REACT_APP_FUNC_ENDPOINT=https://zhijieapi.internal.proudcliff-bba63f36.centralus.azurecontainerapps.io" + - "PORT=80" + api: + build: + context: ./api + dockerfile: Dockerfile + image: $DOCKER_REGISTRY/hello-world-tab-api + container_name: hello-world-tab-api \ No newline at end of file diff --git a/hello-world-tab-docker/env/.env.dev b/hello-world-tab-docker/env/.env.dev new file mode 100644 index 00000000..0ca79914 --- /dev/null +++ b/hello-world-tab-docker/env/.env.dev @@ -0,0 +1,28 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev +TEAMS_APP_NAME=Hello_World_Tab_with_Backend +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly +TEAMS_APP_ID= +TEAMS_APP_TENANT_ID= +AAD_APP_CLIENT_ID= +AAD_APP_OBJECT_ID= +AAD_APP_ACCESS_AS_USER_PERMISSION_ID= +AAD_APP_TENANT_ID= +AAD_APP_OAUTH_AUTHORITY_HOST= +AAD_APP_OAUTH_AUTHORITY= +TAB_ENDPOINT= +TAB_DOMAIN= +API_FUNCTION_ENDPOINT= +BACKEND_APP_NAME= +FRONTEND_APP_NAME= +REGISTRY_NAME= +TAB_CONTAINER_IMAGE= +API_CONTAINER_IMAGE= \ No newline at end of file diff --git a/hello-world-tab-docker/infra/azure.bicep b/hello-world-tab-docker/infra/azure.bicep new file mode 100644 index 00000000..39fb518f --- /dev/null +++ b/hello-world-tab-docker/infra/azure.bicep @@ -0,0 +1,181 @@ +param resourceBaseName string + +param aadAppClientId string +param aadAppTenantId string +param aadAppOauthAuthorityHost string +@secure() +param aadAppClientSecret string + +param location string = resourceGroup().location +var oauthAuthority = uri(aadAppOauthAuthorityHost, aadAppTenantId) + +@description('Specifies the docker container image to deploy.') +param containerImage string = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + +@description('Minimum number of replicas that will be deployed') +@minValue(0) +@maxValue(25) +param minReplica int = 1 + +@description('Maximum number of replicas that will be deployed') +@minValue(0) +@maxValue(25) +param maxReplica int = 3 + +var teamsMobileOrDesktopAppClientId = '1fec8e78-bce4-4aaf-ab1b-5451cc387264' +var teamsWebAppClientId = '5e3ce6c0-2b1f-4285-8d4b-75ee78787346' +var officeWebAppClientId1 = '4345a7b9-9a63-4910-a426-35363201d503' +var officeWebAppClientId2 = '4765445b-32c6-49b0-83e6-1d93765276ca' +var outlookDesktopAppClientId = 'd3590ed6-52b3-4102-aeff-aad2292ab01c' +var outlookWebAppClientId = '00000002-0000-0ff1-ce00-000000000000' +var authorizedClientApplicationIds = '${teamsMobileOrDesktopAppClientId};${teamsWebAppClientId};${officeWebAppClientId1};${officeWebAppClientId2};${outlookDesktopAppClientId};${outlookWebAppClientId}' +var allowedClientApplications = '"${teamsMobileOrDesktopAppClientId}","${teamsWebAppClientId}","${officeWebAppClientId1}","${officeWebAppClientId2}","${outlookDesktopAppClientId}","${outlookWebAppClientId}"' + +module acr 'containerRegistry.bicep' = { + name: 'acr' + params: { + containerRegistryName: resourceBaseName + } +} + +resource containerAppEnv 'Microsoft.App/managedEnvironments@2023-05-01' = { + name: resourceBaseName + location: location + properties: {} +} + +resource frontendApp 'Microsoft.App/containerApps@2023-05-01' = { + name: '${resourceBaseName}-frontend' + location: location + properties: { + managedEnvironmentId: containerAppEnv.id + configuration: { + ingress: { + external: true + targetPort: 80 + allowInsecure: false + traffic: [ + { + latestRevision: true + weight: 100 + } + ] + } + } + template: { + containers: [ + { + name: resourceBaseName + image: containerImage + resources: { + cpu: json('.25') + memory: '.5Gi' + } + } + ] + scale: { + minReplicas: minReplica + maxReplicas: maxReplica + rules: [ + { + name: 'http-requests' + http: { + metadata: { + concurrentRequests: '10' + } + } + } + ] + } + } + } +} + +var siteDomain = frontendApp.properties.configuration.ingress.fqdn + +resource containerApp 'Microsoft.App/containerApps@2023-05-01' = { + name: resourceBaseName + location: location + properties: { + managedEnvironmentId: containerAppEnv.id + configuration: { + secrets: [ + { + name: 'allowed-app-ids' + value: authorizedClientApplicationIds + } + { + name: 'm365-client-id' + value: aadAppClientId + } + { + name: 'm365-client-secret' + value: aadAppClientSecret + } + { + name: 'm365-tenant-id' + value: aadAppTenantId + } + { + name: 'm365-oauth-authority-host' + value: aadAppOauthAuthorityHost + } + { + name: 'website-auth-aad-acl' + value: '{"allowed_client_applications": [${allowedClientApplications}]}' + } + ] + ingress: { + external: true + targetPort: 80 + allowInsecure: false + traffic: [ + { + latestRevision: true + weight: 100 + } + ] + corsPolicy: { + allowCredentials: true + allowedOrigins: [ + siteDomain + ] + } + } + } + template: { + containers: [ + { + name: resourceBaseName + image: containerImage + resources: { + cpu: json('.25') + memory: '.5Gi' + } + } + ] + scale: { + minReplicas: minReplica + maxReplicas: maxReplica + rules: [ + { + name: 'http-requests' + http: { + metadata: { + concurrentRequests: '10' + } + } + } + ] + } + } + } +} + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output REGISTRY_NAME string = acr.outputs.name +output API_FUNCTION_ENDPOINT string = 'https://${containerApp.properties.configuration.ingress.fqdn}' +output BACKEND_APP_NAME string = containerApp.name +output FRONTEND_APP_NAME string = frontendApp.name +output TAB_DOMAIN string = siteDomain +output TAB_ENDPOINT string = 'https://${siteDomain}' diff --git a/hello-world-tab-docker/infra/azure.parameters.json b/hello-world-tab-docker/infra/azure.parameters.json new file mode 100644 index 00000000..14d50e1c --- /dev/null +++ b/hello-world-tab-docker/infra/azure.parameters.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "helloworld${{RESOURCE_SUFFIX}}" + }, + "aadAppClientId": { + "value": "${{AAD_APP_CLIENT_ID}}" + }, + "aadAppClientSecret": { + "value": "${{SECRET_AAD_APP_CLIENT_SECRET}}" + }, + "aadAppTenantId": { + "value": "${{AAD_APP_TENANT_ID}}" + }, + "aadAppOauthAuthorityHost": { + "value": "${{AAD_APP_OAUTH_AUTHORITY_HOST}}" + } + } +} diff --git a/hello-world-tab-docker/infra/containerRegistry.bicep b/hello-world-tab-docker/infra/containerRegistry.bicep new file mode 100644 index 00000000..f25fbf53 --- /dev/null +++ b/hello-world-tab-docker/infra/containerRegistry.bicep @@ -0,0 +1,19 @@ +param containerRegistryName string + +var location = resourceGroup().location + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-12-01' = { + name: containerRegistryName + location: location + sku: { + name: 'Standard' + } + properties: { + //You will need to enable an admin user account in your Azure Container Registry even when you use an Azure managed identity https://docs.microsoft.com/azure/container-apps/containers + adminUserEnabled: true + } +} + +output id string = containerRegistry.id +output name string = containerRegistry.name +output loginServer string = containerRegistry.properties.loginServer diff --git a/hello-world-tab-docker/package.json b/hello-world-tab-docker/package.json new file mode 100644 index 00000000..2b4fa816 --- /dev/null +++ b/hello-world-tab-docker/package.json @@ -0,0 +1,56 @@ +{ + "name": "teamsfx-template-tab", + "version": "0.1.0", + "engines": { + "node": "16 || 18" + }, + "private": true, + "dependencies": { + "@fluentui/react-components": "^9.18.0", + "@microsoft/teams-js": "^2.7.1", + "@microsoft/teamsfx": "^2.2.0", + "@microsoft/teamsfx-react": "^3.0.0", + "axios": "^0.21.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.8.0", + "react-scripts": "^5.0.1" + }, + "devDependencies": { + "@types/node": "^14.0.0", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "@types/react-router-dom": "^5.3.3", + "concurrently": "^8.2.2", + "env-cmd": "^10.1.0", + "typescript": "^4.1.2" + }, + "scripts": { + "dev:teamsfx": "concurrently \"npm run dev-tab:teamsfx\" \"npm run dev-api:teamsfx\"", + "dev-tab:teamsfx": "env-cmd --silent -f .localConfigs npm run start", + "dev-api:teamsfx": "cd api && npm run dev:teamsfx", + "start": "react-scripts start", + "build": "react-scripts build", + "test": "echo \"Error: no test specified\" && exit 1", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "homepage": "." +} diff --git a/hello-world-tab-docker/public/auth-end.html b/hello-world-tab-docker/public/auth-end.html new file mode 100644 index 00000000..5c283022 --- /dev/null +++ b/hello-world-tab-docker/public/auth-end.html @@ -0,0 +1,56 @@ + + + + + Login End Page + + + + + + + + + diff --git a/hello-world-tab-docker/public/auth-start.html b/hello-world-tab-docker/public/auth-start.html new file mode 100644 index 00000000..5ef668fd --- /dev/null +++ b/hello-world-tab-docker/public/auth-start.html @@ -0,0 +1,53 @@ + + + + + Login Start Page + + + + + + + + + diff --git a/hello-world-tab-docker/public/deploy.png b/hello-world-tab-docker/public/deploy.png new file mode 100644 index 00000000..130a2ae4 Binary files /dev/null and b/hello-world-tab-docker/public/deploy.png differ diff --git a/hello-world-tab-docker/public/favicon.ico b/hello-world-tab-docker/public/favicon.ico new file mode 100644 index 00000000..ef5ef2b4 Binary files /dev/null and b/hello-world-tab-docker/public/favicon.ico differ diff --git a/hello-world-tab-docker/public/hello.png b/hello-world-tab-docker/public/hello.png new file mode 100644 index 00000000..8654be8a Binary files /dev/null and b/hello-world-tab-docker/public/hello.png differ diff --git a/hello-world-tab-docker/public/index.html b/hello-world-tab-docker/public/index.html new file mode 100644 index 00000000..c61bcb44 --- /dev/null +++ b/hello-world-tab-docker/public/index.html @@ -0,0 +1,17 @@ + + + + + + + Microsoft Teams Tab + + + + +
+ + diff --git a/hello-world-tab-docker/public/publish.png b/hello-world-tab-docker/public/publish.png new file mode 100644 index 00000000..1ba94af3 Binary files /dev/null and b/hello-world-tab-docker/public/publish.png differ diff --git a/hello-world-tab-docker/src/components/App.tsx b/hello-world-tab-docker/src/components/App.tsx new file mode 100644 index 00000000..e39828ba --- /dev/null +++ b/hello-world-tab-docker/src/components/App.tsx @@ -0,0 +1,65 @@ +// https://fluentsite.z22.web.core.windows.net/quick-start +import { + FluentProvider, + teamsLightTheme, + teamsDarkTheme, + teamsHighContrastTheme, + tokens, +} from "@fluentui/react-components"; +import { useEffect } from "react"; +import { HashRouter as Router, Navigate, Route, Routes } from "react-router-dom"; +import { app } from "@microsoft/teams-js"; +import { useTeamsUserCredential } from "@microsoft/teamsfx-react"; +import Privacy from "./Privacy"; +import TermsOfUse from "./TermsOfUse"; +import Tab from "./Tab"; +import TabConfig from "./TabConfig"; +import { TeamsFxContext } from "./Context"; +import config from "./sample/lib/config"; + +/** + * The main app which handles the initialization and routing + * of the app. + */ +export default function App() { + const { loading, theme, themeString, teamsUserCredential } = useTeamsUserCredential({ + initiateLoginEndpoint: config.initiateLoginEndpoint!, + clientId: config.clientId!, + }); + useEffect(() => { + loading && + app.initialize().then(() => { + // Hide the loading indicator. + app.notifySuccess(); + }); + }, [loading]); + return ( + + + + {!loading && ( + + } /> + } /> + } /> + } /> + }> + + )} + + + + ); +} diff --git a/hello-world-tab-docker/src/components/Context.tsx b/hello-world-tab-docker/src/components/Context.tsx new file mode 100644 index 00000000..89224df4 --- /dev/null +++ b/hello-world-tab-docker/src/components/Context.tsx @@ -0,0 +1,13 @@ +import { TeamsUserCredential } from "@microsoft/teamsfx"; +import { createContext } from "react"; +import { Theme } from "@fluentui/react-components"; + +export const TeamsFxContext = createContext<{ + theme?: Theme; + themeString: string; + teamsUserCredential?: TeamsUserCredential; +}>({ + theme: undefined, + themeString: "", + teamsUserCredential: undefined, +}); diff --git a/hello-world-tab-docker/src/components/Privacy.tsx b/hello-world-tab-docker/src/components/Privacy.tsx new file mode 100644 index 00000000..048cb6f0 --- /dev/null +++ b/hello-world-tab-docker/src/components/Privacy.tsx @@ -0,0 +1,17 @@ +import React from "react"; +/** + * This component is used to display the required + * privacy statement which can be found in a link in the + * about tab. + */ +class Privacy extends React.Component { + render() { + return ( +
+

Privacy Statement

+
+ ); + } +} + +export default Privacy; diff --git a/hello-world-tab-docker/src/components/Tab.tsx b/hello-world-tab-docker/src/components/Tab.tsx new file mode 100644 index 00000000..40eb111d --- /dev/null +++ b/hello-world-tab-docker/src/components/Tab.tsx @@ -0,0 +1,17 @@ +import { useContext } from "react"; +import { Welcome } from "./sample/Welcome"; +import { TeamsFxContext } from "./Context"; +import config from "./sample/lib/config"; + +const showFunction = Boolean(config.apiName); + +export default function Tab() { + const { themeString } = useContext(TeamsFxContext); + return ( +
+ +
+ ); +} diff --git a/hello-world-tab-docker/src/components/TabConfig.tsx b/hello-world-tab-docker/src/components/TabConfig.tsx new file mode 100644 index 00000000..9e70b7ff --- /dev/null +++ b/hello-world-tab-docker/src/components/TabConfig.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { app, pages } from "@microsoft/teams-js"; + +/** + * The 'Config' component is used to display your group tabs + * user configuration options. Here you will allow the user to + * make their choices and once they are done you will need to validate + * their choices and communicate that to Teams to enable the save button. + */ +class TabConfig extends React.Component { + render() { + // Initialize the Microsoft Teams SDK + app.initialize().then(() => { + /** + * When the user clicks "Save", save the url for your configured tab. + * This allows for the addition of query string parameters based on + * the settings selected by the user. + */ + pages.config.registerOnSaveHandler((saveEvent) => { + const baseUrl = `https://${window.location.hostname}:${window.location.port}`; + pages.config + .setConfig({ + suggestedDisplayName: "My Tab", + entityId: "Test", + contentUrl: baseUrl + "/index.html#/tab", + websiteUrl: baseUrl + "/index.html#/tab", + }) + .then(() => { + saveEvent.notifySuccess(); + }); + }); + + /** + * After verifying that the settings for your tab are correctly + * filled in by the user you need to set the state of the dialog + * to be valid. This will enable the save button in the configuration + * dialog. + */ + pages.config.setValidityState(true); + }); + + return ( +
+

Tab Configuration

+
+ This is where you will add your tab configuration options the user can choose when the tab + is added to your team/group chat. +
+
+ ); + } +} + +export default TabConfig; diff --git a/hello-world-tab-docker/src/components/TermsOfUse.tsx b/hello-world-tab-docker/src/components/TermsOfUse.tsx new file mode 100644 index 00000000..f3a5c10a --- /dev/null +++ b/hello-world-tab-docker/src/components/TermsOfUse.tsx @@ -0,0 +1,17 @@ +import React from "react"; +/** + * This component is used to display the required + * terms of use statement which can be found in a + * link in the about tab. + */ +class TermsOfUse extends React.Component { + render() { + return ( +
+

Terms of Use

+
+ ); + } +} + +export default TermsOfUse; diff --git a/hello-world-tab-docker/src/components/sample/AzureFunctions.tsx b/hello-world-tab-docker/src/components/sample/AzureFunctions.tsx new file mode 100644 index 00000000..7e41a65a --- /dev/null +++ b/hello-world-tab-docker/src/components/sample/AzureFunctions.tsx @@ -0,0 +1,108 @@ +import { useContext, useState } from "react"; +import { Button, Spinner } from "@fluentui/react-components"; +import { useData } from "@microsoft/teamsfx-react"; +import * as axios from "axios"; +import { BearerTokenAuthProvider, createApiClient, TeamsUserCredential } from "@microsoft/teamsfx"; +import { TeamsFxContext } from "../Context"; +import config from "./lib/config"; + +const functionName = config.apiName || "myFunc"; + +async function callFunction(teamsUserCredential: TeamsUserCredential) { + try { + const apiBaseUrl = config.apiEndpoint + "/api/"; + // createApiClient(...) creates an Axios instance which uses BearerTokenAuthProvider to inject token to request header + const apiClient = createApiClient( + apiBaseUrl, + new BearerTokenAuthProvider(async () => (await teamsUserCredential.getToken(""))!.token) + ); + const response = await apiClient.get(functionName); + return response.data; + } catch (err: unknown) { + if (axios.default.isAxiosError(err)) { + let funcErrorMsg = ""; + + if (err?.response?.status === 404) { + funcErrorMsg = `There may be a problem with the deployment of Azure Function App, please deploy Azure Function (Run command palette "Teams: Deploy") first before running this App`; + } else if (err.message === "Network Error") { + funcErrorMsg = + "Cannot call Azure Function due to network error, please check your network connection status and "; + if (err.config?.url && err.config.url.indexOf("localhost") >= 0) { + funcErrorMsg += `make sure to start Azure Function locally (Run "npm run start" command inside api folder from terminal) first before running this App`; + } else { + funcErrorMsg += `make sure to provision and deploy Azure Function (Run command palette "Teams: Provision" and "Teams: Deploy") first before running this App`; + } + } else { + funcErrorMsg = err.message; + if (err.response?.data?.error) { + funcErrorMsg += ": " + err.response.data.error; + } + } + + throw new Error(funcErrorMsg); + } + throw err; + } +} + +export function AzureFunctions(props: { codePath?: string; docsUrl?: string }) { + const [needConsent, setNeedConsent] = useState(false); + const { codePath, docsUrl } = { + codePath: `api/${functionName}/index.ts`, + docsUrl: "https://aka.ms/teamsfx-azure-functions", + ...props, + }; + const teamsUserCredential = useContext(TeamsFxContext).teamsUserCredential; + const { loading, data, error, reload } = useData(async () => { + if (!teamsUserCredential) { + throw new Error("TeamsFx SDK is not initialized."); + } + if (needConsent) { + await teamsUserCredential!.login(["User.Read"]); + setNeedConsent(false); + } + try { + const functionRes = await callFunction(teamsUserCredential); + return functionRes; + } catch (error: any) { + if (error.message.includes("The application may not be authorized.")) { + setNeedConsent(true); + } + } + }); + return ( +
+

Call your Azure Function

+

+ An Azure Functions app is running. Authorize this app and click below to call it for a + response: +

+ {!loading && ( + + )} + {loading && ( +
+          
+        
+ )} + {!loading && !!data && !error &&
{JSON.stringify(data, null, 2)}
} + {!loading && !data && !error &&
}
+      {!loading && !!error && 
{(error as any).toString()}
} +

How to edit the Azure Function

+

+ See the code in {codePath} to add your business logic. +

+ {!!docsUrl && ( +

+ For more information, see the{" "} + + docs + + . +

+ )} +
+ ); +} diff --git a/hello-world-tab-docker/src/components/sample/CurrentUser.tsx b/hello-world-tab-docker/src/components/sample/CurrentUser.tsx new file mode 100644 index 00000000..ca6a044d --- /dev/null +++ b/hello-world-tab-docker/src/components/sample/CurrentUser.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +export function CurrentUser(props: { userName?: string }) { + const { userName } = { + userName: "", + ...props, + }; + return ( +
+

Get the current user

+

Access basic information about the user like this:

+
+        {`const authConfig: TeamsUserCredentialAuthConfig = {\n  clientId: process.env.REACT_APP_CLIENT_ID,\n  initiateLoginEndpoint: process.env.REACT_APP_START_LOGIN_PAGE_URL,\n};\n\nconst credential = new TeamsUserCredential(authConfig);\nconst user = await credential.getUserInfo();`}
+      
+ {!!userName && ( +

+ The currently logged in user's name is {userName} +

+ )} +
+ ); +} diff --git a/hello-world-tab-docker/src/components/sample/Deploy.css b/hello-world-tab-docker/src/components/sample/Deploy.css new file mode 100644 index 00000000..2891c455 --- /dev/null +++ b/hello-world-tab-docker/src/components/sample/Deploy.css @@ -0,0 +1,5 @@ +.deploy.page > img { + margin: 0 auto; + display: block; + width: 100%; +} diff --git a/hello-world-tab-docker/src/components/sample/Deploy.tsx b/hello-world-tab-docker/src/components/sample/Deploy.tsx new file mode 100644 index 00000000..56ab5b8e --- /dev/null +++ b/hello-world-tab-docker/src/components/sample/Deploy.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import "./Deploy.css"; +import { Image } from "@fluentui/react-components"; + +export function Deploy(props: { docsUrl?: string }) { + const { docsUrl } = { + docsUrl: "https://aka.ms/teamsfx-docs", + ...props, + }; + return ( +
+

Deploy to the Cloud

+

+ Before publishing your app to Teams App Catalog, you may want to provision and deploy your + app's resources to the cloud to make sure your app will be running smoothly! +

+

+ To provision your resources, you can either use our CLI command "teamsapp provision" or apply + "Teams: Provision" in Command palette. +

+

+ To deploy your app, you can either use our CLI command "teamsapp deploy" or apply "Teams: + Deploy" in Command palette. +

+ +

+ For more information, see the{" "} + + docs + + . +

+
+ ); +} diff --git a/hello-world-tab-docker/src/components/sample/EditCode.tsx b/hello-world-tab-docker/src/components/sample/EditCode.tsx new file mode 100644 index 00000000..f042f70a --- /dev/null +++ b/hello-world-tab-docker/src/components/sample/EditCode.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import config from "./lib/config"; + +const functionName = config.apiName || "myFunc"; + +export function EditCode(props: { + showFunction?: boolean; + tabCodeEntry?: string; + functionCodePath?: string; +}) { + const { showFunction, tabCodeEntry, functionCodePath } = { + showFunction: true, + tabCodeEntry: "tabs/src/index.tsx", + functionCodePath: `api/${functionName}/index.ts`, + ...props, + }; + return ( +
+

Change this code

+

+ The front end is a create-react-app. The entry point is{" "} + {tabCodeEntry}. Just save any file and this page will reload automatically. +

+ {showFunction && ( +

+ This app contains an Azure Functions backend. Find the code in{" "} + {functionCodePath} +

+ )} +
+ ); +} diff --git a/hello-world-tab-docker/src/components/sample/Publish.css b/hello-world-tab-docker/src/components/sample/Publish.css new file mode 100644 index 00000000..6b8c5f46 --- /dev/null +++ b/hello-world-tab-docker/src/components/sample/Publish.css @@ -0,0 +1,5 @@ +.publish.page > img { + margin: 0 auto; + display: block; + width: 100%; +} diff --git a/hello-world-tab-docker/src/components/sample/Publish.tsx b/hello-world-tab-docker/src/components/sample/Publish.tsx new file mode 100644 index 00000000..493acb6e --- /dev/null +++ b/hello-world-tab-docker/src/components/sample/Publish.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import "./Publish.css"; +import { Image } from "@fluentui/react-components"; + +export function Publish(props: { docsUrl?: string }) { + const { docsUrl } = { + docsUrl: "https://aka.ms/teamsfx-docs", + ...props, + }; + return ( +
+

Publish to Teams

+

+ Your app's resources and infrastructure are deployed and ready? Publish and register your + app to Teams app catalog to share your app with others in your organization! +

+ +

+ For more information, see the{" "} + + docs + + . +

+
+ ); +} diff --git a/hello-world-tab-docker/src/components/sample/Welcome.css b/hello-world-tab-docker/src/components/sample/Welcome.css new file mode 100644 index 00000000..2010d621 --- /dev/null +++ b/hello-world-tab-docker/src/components/sample/Welcome.css @@ -0,0 +1,99 @@ +.narrow { + width: 900px; + margin: 0 auto; +} + +.page-padding { + padding: 4rem; +} + +.welcome.page > .narrow > img { + margin: 0 auto; + display: block; + width: 200px; +} + +.welcome.page > .narrow > ul { + width: 80%; + justify-content: space-between; + margin: 4rem auto; + border-bottom: none; +} + +.welcome.page > .narrow > ul > li { + background-color: inherit; + margin: auto; +} + +.welcome.page > .narrow > ul > li > a { + font-size: 14px; + min-height: 32px; + border-bottom-color: rgb(98, 100, 167); +} + +.center { + text-align: center; +} + +.sections > * { + margin: 4rem auto; +} + +.tabList { + align-items: center; + display: flex; + flex-direction: column; + justify-content: flex-start; + padding: 30px 20px; + row-gap: 30px; +} + +pre, +div.error { + background-color: #e5e5e5; + padding: 1rem; + box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); + border-radius: 3px; + margin: 1rem 0; + max-height: 200px; + overflow-x: scroll; + overflow-y: scroll; + max-width: 732px; +} + +pre.fixed, +div.error.fixed { + height: 200px; +} + +code { + background-color: #e5e5e5; + display: inline-block; + padding: 0px 6px; + border-radius: 3px; + box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); +} + +code::-webkit-scrollbar, +pre::-webkit-scrollbar { + display: none; +} + +.contrast pre, +.contrast code, +.contrast div.error { + background-color: #000000; + border-color: #ffffff; + border-width: thin; + border-style: solid; +} + +.dark pre, +.dark code, +.dark div.error { + background-color: #1b1b1b; +} + +.error { + color: red; +} diff --git a/hello-world-tab-docker/src/components/sample/Welcome.tsx b/hello-world-tab-docker/src/components/sample/Welcome.tsx new file mode 100644 index 00000000..841b5539 --- /dev/null +++ b/hello-world-tab-docker/src/components/sample/Welcome.tsx @@ -0,0 +1,86 @@ +import { useContext, useState } from "react"; +import { + Image, + TabList, + Tab, + SelectTabEvent, + SelectTabData, + TabValue, +} from "@fluentui/react-components"; +import "./Welcome.css"; +import { EditCode } from "./EditCode"; +import { AzureFunctions } from "./AzureFunctions"; +import { CurrentUser } from "./CurrentUser"; +import { useData } from "@microsoft/teamsfx-react"; +import { Deploy } from "./Deploy"; +import { Publish } from "./Publish"; +import { TeamsFxContext } from "../Context"; + +export function Welcome(props: { showFunction?: boolean; environment?: string }) { + const { showFunction, environment } = { + showFunction: true, + environment: window.location.hostname === "localhost" ? "local" : "azure", + ...props, + }; + const friendlyEnvironmentName = + { + local: "local environment", + azure: "Azure environment", + }[environment] || "local environment"; + + const [selectedValue, setSelectedValue] = useState("local"); + + const onTabSelect = (event: SelectTabEvent, data: SelectTabData) => { + setSelectedValue(data.value); + }; + const { teamsUserCredential } = useContext(TeamsFxContext); + const { loading, data, error } = useData(async () => { + if (teamsUserCredential) { + const userInfo = await teamsUserCredential.getUserInfo(); + return userInfo; + } + }); + const userName = loading || error ? "" : data!.displayName; + return ( +
+
+ +

Congratulations{userName ? ", " + userName : ""}!

+

Your app is running in your {friendlyEnvironmentName}

+ +
+ + + 1. Build your app locally + + + 2. Provision and Deploy to the Cloud + + + 3. Publish to Teams + + +
+ {selectedValue === "local" && ( +
+ + + {showFunction && } +
+ )} + {selectedValue === "azure" && ( +
+ +
+ )} + {selectedValue === "publish" && ( +
+ +
+ )} +
+
+
+
+ ); +} diff --git a/hello-world-tab-docker/src/components/sample/lib/config.ts b/hello-world-tab-docker/src/components/sample/lib/config.ts new file mode 100644 index 00000000..1e43cf3f --- /dev/null +++ b/hello-world-tab-docker/src/components/sample/lib/config.ts @@ -0,0 +1,8 @@ +const config = { + initiateLoginEndpoint: process.env.REACT_APP_START_LOGIN_PAGE_URL, + clientId: process.env.REACT_APP_CLIENT_ID, + apiEndpoint: process.env.REACT_APP_FUNC_ENDPOINT, + apiName: process.env.REACT_APP_FUNC_NAME, +}; + +export default config; diff --git a/hello-world-tab-docker/src/index.css b/hello-world-tab-docker/src/index.css new file mode 100644 index 00000000..eed3a419 --- /dev/null +++ b/hello-world-tab-docker/src/index.css @@ -0,0 +1,18 @@ +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +@media only screen and (max-width: 768px) { + body { + width: fit-content; + } +} diff --git a/hello-world-tab-docker/src/index.tsx b/hello-world-tab-docker/src/index.tsx new file mode 100644 index 00000000..c13a3063 --- /dev/null +++ b/hello-world-tab-docker/src/index.tsx @@ -0,0 +1,8 @@ +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./components/App"; +import "./index.css"; + +const container = document.getElementById("root"); +const root = createRoot(container!); +root.render(); diff --git a/hello-world-tab-docker/teamsapp.local.yml b/hello-world-tab-docker/teamsapp.local.yml new file mode 100644 index 00000000..78bbc088 --- /dev/null +++ b/hello-world-tab-docker/teamsapp.local.yml @@ -0,0 +1,96 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.2/yaml.schema.json +version: v1.2 + +additionalMetadata: + sampleTag: TeamsFx-Samples:hello-world-tab-with-backend + +environmentFolderPath: ./env + +provision: + - uses: aadApp/create # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty + with: + name: hello-world-tab-with-backend-aad # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here. + generateClientSecret: true # If the value is false, the action will not generate client secret for you + signInAudience: "AzureADMyOrg" # Authenticate users with a Microsoft work or school account in your organization's Azure AD tenant (for example, single tenant). + writeToEnvironmentFile: # Write the information of created resources into environment file for the specified environment variable(s). + clientId: AAD_APP_CLIENT_ID + clientSecret: SECRET_AAD_APP_CLIENT_SECRET # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file + objectId: AAD_APP_OBJECT_ID + tenantId: AAD_APP_TENANT_ID + authority: AAD_APP_OAUTH_AUTHORITY + authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST + + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: tab-with-backend${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + - uses: script # Set required variables for local launch + with: + run: + echo "::set-teamsfx-env TAB_DOMAIN=localhost:53000"; + echo "::set-teamsfx-env TAB_ENDPOINT=https://localhost:53000"; + echo "::set-teamsfx-env FUNC_NAME=getUserProfile"; + echo "::set-teamsfx-env FUNC_ENDPOINT=http://localhost:7071"; + + - uses: aadApp/update # Apply the AAD manifest to an existing AAD app. Will use the object id in manifest file to determine which AAD app to update. + with: + manifestPath: aad.manifest.json # Relative path to teamsfx folder. Environment variables in manifest will be replaced before apply to AAD app + outputFilePath: build/aad.manifest.${{TEAMSFX_ENV}}.json + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip # Relative path to teamsfx folder. This is the path for built zip file. + + # Install development tool(s) + - uses: devTool/install + with: + devCert: + trust: true + # Write the information of installed development tool(s) into environment + # file for the specified environment variable(s). + writeToEnvironmentFile: + sslCertFile: SSL_CRT_FILE + sslKeyFile: SSL_KEY_FILE + + # Generate runtime environment variables for tab + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs + envs: + HTTPS: true + PORT: 53000 + REACT_APP_CLIENT_ID: ${{AAD_APP_CLIENT_ID}} + REACT_APP_START_LOGIN_PAGE_URL: ${{TAB_ENDPOINT}}/auth-start.html + REACT_APP_FUNC_NAME: ${{FUNC_NAME}} + + # Generate runtime environment variables for backend + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./api/.localConfigs + envs: + M365_CLIENT_ID: ${{AAD_APP_CLIENT_ID}} + M365_CLIENT_SECRET: ${{SECRET_AAD_APP_CLIENT_SECRET}} + M365_TENANT_ID: ${{AAD_APP_TENANT_ID}} + M365_AUTHORITY_HOST: ${{AAD_APP_OAUTH_AUTHORITY_HOST}} + ALLOWED_APP_IDS: 1fec8e78-bce4-4aaf-ab1b-5451cc387264;5e3ce6c0-2b1f-4285-8d4b-75ee78787346;0ec893e0-5785-4de6-99da-4ed124e5296c;4345a7b9-9a63-4910-a426-35363201d503;4765445b-32c6-49b0-83e6-1d93765276ca;d3590ed6-52b3-4102-aeff-aad2292ab01c;00000002-0000-0ff1-ce00-000000000000;bc59ab01-8403-45c6-8796-ac3ef710b3e3 diff --git a/hello-world-tab-docker/teamsapp.yml b/hello-world-tab-docker/teamsapp.yml new file mode 100644 index 00000000..52a64706 --- /dev/null +++ b/hello-world-tab-docker/teamsapp.yml @@ -0,0 +1,163 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.2/yaml.schema.json +version: v1.2 + +additionalMetadata: + sampleTag: TeamsFx-Samples:hello-world-tab-with-backend + +environmentFolderPath: ./env + +provision: + - uses: aadApp/create # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty + with: + name: hello-world-tab-with-backend-aad # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here. + generateClientSecret: true # If the value is false, the action will not generate client secret for you + signInAudience: "AzureADMyOrg" # Authenticate users with a Microsoft work or school account in your organization's Azure AD tenant (for example, single tenant). + writeToEnvironmentFile: + # Write the information of created resources into environment file for the specified environment variable(s). + clientId: AAD_APP_CLIENT_ID + clientSecret: SECRET_AAD_APP_CLIENT_SECRET # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file + objectId: AAD_APP_OBJECT_ID + tenantId: AAD_APP_TENANT_ID + authority: AAD_APP_OAUTH_AUTHORITY + authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST + + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: tab-with-backend${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} # The AZURE_SUBSCRIPTION_ID is a built-in environment variable. TeamsFx will ask you select one subscription if its value is empty. You're free to reference other environment varialbe here, but TeamsFx will not ask you to select subscription if it's empty in this case. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} # The AZURE_RESOURCE_GROUP_NAME is a built-in environment variable. TeamsFx will ask you to select or create one resource group if its value is empty. You're free to reference other environment varialbe here, but TeamsFx will not ask you to select or create resource grouop if it's empty in this case. + templates: + - path: infra/azure.bicep # Relative path to teamsfx folder + parameters: infra/azure.parameters.json # Relative path to teamsfx folder. Placeholders will be replaced with corresponding environment variable before ARM deployment. + deploymentName: Create-resources-for-tab # Required when deploy ARM template + bicepCliVersion: v0.9.1 # Teams Toolkit will download this bicep CLI version from github for you, will use bicep CLI in PATH if you remove this config. + + - uses: aadApp/update # Apply the AAD manifest to an existing AAD app. Will use the object id in manifest file to determine which AAD app to update. + with: + manifestPath: aad.manifest.json # Relative path to teamsfx folder. Environment variables in manifest will be replaced before apply to AAD app + outputFilePath: build/aad.manifest.${{TEAMSFX_ENV}}.json + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip # Relative path to teamsfx folder. This is the path for built zip file. + +deploy: + # Generate runtime environment variables for tab + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.env.production + envs: + REACT_APP_CLIENT_ID: ${{AAD_APP_CLIENT_ID}} + REACT_APP_START_LOGIN_PAGE_URL: ${{TAB_ENDPOINT}}/auth-start.html + REACT_APP_FUNC_NAME: getUserProfile + REACT_APP_FUNC_ENDPOINT: ${{API_FUNCTION_ENDPOINT}} + + - uses: script + name: set environment variables + with: + run: echo "::set-teamsfx-env + TAB_CONTAINER_IMAGE=${{REGISTRY_NAME}}.azurecr.io/hello_world_tab" + + - uses: script + name: set environment variables + with: + run: echo "::set-teamsfx-env + API_CONTAINER_IMAGE=${{REGISTRY_NAME}}.azurecr.io/hello_world_api" + + - uses: script + name: build container image + with: + run: docker build -t ${{TAB_CONTAINER_IMAGE}} . + + - uses: script + name: build container image + with: + run: docker build -t ${{API_CONTAINER_IMAGE}} ./api + + - uses: script + name: login azure container registry + with: + run: az acr login -n ${{REGISTRY_NAME}} + + - uses: script + name: push container image + with: + run: docker push ${{TAB_CONTAINER_IMAGE}} + + - uses: script + name: push container image + with: + run: docker push ${{API_CONTAINER_IMAGE}} + + - uses: script + name: deploy container image + with: + run: az containerapp up -n ${{FRONTEND_APP_NAME}} --image ${{TAB_CONTAINER_IMAGE}} + + - uses: script + name: deploy container image + with: + run: az containerapp up -n ${{BACKEND_APP_NAME}} --image ${{API_CONTAINER_IMAGE}} --env-vars "M365_CLIENT_ID=secretref:m365-client-id" + "M365_CLIENT_SECRET=secretref:m365-client-secret" + "M365_TENANT_ID=secretref:m365-tenant-id" + "M365_AUTHORITY_HOST=secretref:m365-oauth-authority-host" + "WEBSITE_AUTH_AAD_ACL=secretref:website-auth-aad-acl" + "ALLOWED_APP_IDS=secretref:allowed-app-ids" + "CORS_ALLOWED_ORIGINS=[\"${{TAB_ENDPOINT}}\"]" + "CORS_SUPPORT_CREDENTIALS=true" + "CONTAINER_NAME=api" + +publish: + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Publish the app to + # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps) + # for review and approval + - uses: teamsApp/publishAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + publishedAppId: TEAMS_APP_PUBLISHED_APP_ID +projectId: ec44a81a-8784-49bc-95b5-bcbd2e1cd2e5 diff --git a/hello-world-tab-docker/tsconfig.json b/hello-world-tab-docker/tsconfig.json new file mode 100644 index 00000000..9d379a3c --- /dev/null +++ b/hello-world-tab-docker/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"] +} diff --git a/hello-world-teams-tab-and-outlook-add-in/images/teams-tab-app.PNG b/hello-world-teams-tab-and-outlook-add-in/images/teams-tab-app.PNG index 23ad9929..c5e65a9b 100644 Binary files a/hello-world-teams-tab-and-outlook-add-in/images/teams-tab-app.PNG and b/hello-world-teams-tab-and-outlook-add-in/images/teams-tab-app.PNG differ diff --git a/incoming-webhook-notification/assets/sampleDemo.gif b/incoming-webhook-notification/assets/sampleDemo.gif index 7ec11edd..bef9d1ac 100644 Binary files a/incoming-webhook-notification/assets/sampleDemo.gif and b/incoming-webhook-notification/assets/sampleDemo.gif differ diff --git a/intelligent-data-chart-generator/assets/sampleDemo.gif b/intelligent-data-chart-generator/assets/sampleDemo.gif index 8e7c835b..4b4b8487 100644 Binary files a/intelligent-data-chart-generator/assets/sampleDemo.gif and b/intelligent-data-chart-generator/assets/sampleDemo.gif differ diff --git a/intelligent-data-chart-generator/assets/thumbnail.png b/intelligent-data-chart-generator/assets/thumbnail.png index b18e8750..29e04381 100644 Binary files a/intelligent-data-chart-generator/assets/thumbnail.png and b/intelligent-data-chart-generator/assets/thumbnail.png differ diff --git a/large-scale-notification/README.md b/large-scale-notification/README.md index 5d8361fc..725ab3ef 100644 --- a/large-scale-notification/README.md +++ b/large-scale-notification/README.md @@ -1,16 +1,17 @@ --- page_type: sample languages: -- typescript + - typescript products: -- office-teams -- office + - office-teams + - office name: Large Scale Notification Bot urlFragment: officedev-teamsfx-samples-bot-large-scale-notification description: This sample demonstrates a Teams notification bot app created by Teams Toolkit to send individual chat messages to a large number of users. extensions: createdDate: "2023-10-17" --- + # Overview of the Large Scale Notification Bot 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. @@ -47,6 +48,11 @@ To debug the project, you will need to configure an Azure Service Bus to be used 4. Visit https://developer.microsoft.com/en-us/graph/graph-explorer. Click "Modify permissions" and consent "TeamsAppInstallation.ReadWriteForUser" & "TeamsAppInstallation.ReadWriteAndConsentForUser". 5. Copy the "Access token" and paste it to variable `ACCESS_TOKEN` in `script/installAppForUsers.js`. 6. Run command in project folder: `node script/installAppForUsers.js`. + > After publishing your app, it may take several hours before you can retrieve app details using the Graph API, which is utilized in the `installAppForUsers` script. You could check if the app is ready by sending the following request in Graph Explorer, and replace `` with the actual "App Id" from step 2. If the value of `@odata.count` in the response is equal to 1, you are good to run the script. + > + > ``` + > GET https://graph.microsoft.com/v1.0/appCatalogs/teamsApps?$filter=id eq '' + > ``` 7. Check the provisioned Azure Storage Account Table `installation` for installation records. ## Mock the installation of a large amount of users diff --git a/large-scale-notification/assets/thumbnail.png b/large-scale-notification/assets/thumbnail.png index 725cb632..944ddde5 100644 Binary files a/large-scale-notification/assets/thumbnail.png and b/large-scale-notification/assets/thumbnail.png differ diff --git a/live-share-dice-roller/assets/sampleDemo.gif b/live-share-dice-roller/assets/sampleDemo.gif index 72c455fc..33d29436 100644 Binary files a/live-share-dice-roller/assets/sampleDemo.gif and b/live-share-dice-roller/assets/sampleDemo.gif differ diff --git a/live-share-dice-roller/assets/thumbnail.png b/live-share-dice-roller/assets/thumbnail.png index b2c186e0..2e4abc33 100644 Binary files a/live-share-dice-roller/assets/thumbnail.png and b/live-share-dice-roller/assets/thumbnail.png differ diff --git a/query-org-user-with-message-extension-sso/images/link-unfurling.gif b/query-org-user-with-message-extension-sso/images/link-unfurling.gif index 9616738e..2582f900 100644 Binary files a/query-org-user-with-message-extension-sso/images/link-unfurling.gif and b/query-org-user-with-message-extension-sso/images/link-unfurling.gif differ diff --git a/query-org-user-with-message-extension-sso/images/total.gif b/query-org-user-with-message-extension-sso/images/total.gif index 0d8f0798..c58882a3 100644 Binary files a/query-org-user-with-message-extension-sso/images/total.gif and b/query-org-user-with-message-extension-sso/images/total.gif differ diff --git a/react-retail-dashboard/README.md b/react-retail-dashboard/README.md index 3c93fa75..ed60cb88 100644 --- a/react-retail-dashboard/README.md +++ b/react-retail-dashboard/README.md @@ -17,7 +17,7 @@ This is a dashboard sample based on demo sample data that shows you how to creat ## Minimal path to awesome -### Deploy the app to Azure +### Deploy the app >Here are the instructions to run the sample in **Visual Studio Code**. You can also try to run the app using TeamsFx CLI tool, refer to [Try sample with TeamsFx CLI](cli.md) diff --git a/react-retail-dashboard/assets/screenshot.png b/react-retail-dashboard/assets/screenshot.png index cb5a922b..4a274648 100644 Binary files a/react-retail-dashboard/assets/screenshot.png and b/react-retail-dashboard/assets/screenshot.png differ diff --git a/react-retail-dashboard/assets/teams-hosted.png b/react-retail-dashboard/assets/teams-hosted.png index 6bb921bb..75696afe 100644 Binary files a/react-retail-dashboard/assets/teams-hosted.png and b/react-retail-dashboard/assets/teams-hosted.png differ diff --git a/react-retail-dashboard/src/src/webparts/retailDashboard/RetailDashboardWebPart.ts b/react-retail-dashboard/src/src/webparts/retailDashboard/RetailDashboardWebPart.ts index e7e8518c..239f4f39 100644 --- a/react-retail-dashboard/src/src/webparts/retailDashboard/RetailDashboardWebPart.ts +++ b/react-retail-dashboard/src/src/webparts/retailDashboard/RetailDashboardWebPart.ts @@ -78,6 +78,7 @@ export default class RetailDashboardWebPart extends BaseClientSideWebPartHere are the instructions to run the sample in **Visual Studio Code**. You can also try to run the app using TeamsFx CLI tool, refer to [Try sample with TeamsFx CLI](cli.md) diff --git a/spfx-productivity-dashboard/src/assets/screenshot.png b/spfx-productivity-dashboard/src/assets/screenshot.png index 7b50243c..60ef70c4 100644 Binary files a/spfx-productivity-dashboard/src/assets/screenshot.png and b/spfx-productivity-dashboard/src/assets/screenshot.png differ diff --git a/sso-enabled-tab-via-apim-proxy/assets/sampleDemo.gif b/sso-enabled-tab-via-apim-proxy/assets/sampleDemo.gif index 03618d4b..25f032d2 100644 Binary files a/sso-enabled-tab-via-apim-proxy/assets/sampleDemo.gif and b/sso-enabled-tab-via-apim-proxy/assets/sampleDemo.gif differ diff --git a/sso-enabled-tab-via-apim-proxy/assets/thumbnail.png b/sso-enabled-tab-via-apim-proxy/assets/thumbnail.png index 7b3e2cf5..e176488e 100644 Binary files a/sso-enabled-tab-via-apim-proxy/assets/thumbnail.png and b/sso-enabled-tab-via-apim-proxy/assets/thumbnail.png differ diff --git a/stocks-update-notification-bot-dotnet/README.md b/stocks-update-notification-bot-dotnet/README.md index e5e4326d..6f999739 100644 --- a/stocks-update-notification-bot-dotnet/README.md +++ b/stocks-update-notification-bot-dotnet/README.md @@ -15,7 +15,7 @@ extensions: Stocks Update Notifcation Bot is a an application that can be installed in different Microsoft Teams contexts, personal, group chat, or teams channel, which sends a message on a pre-defined schedule as an Adaptive Card to locations where it is installed. -![Stocks Update Notification Bot Overview](images/app.gif) +![Stocks Update Notification Bot Overview](images/app.png) ## This sample illustrates - How to use API client in TeamsFx to get access data in public API using the API Key provider. diff --git a/stocks-update-notification-bot-dotnet/images/app.gif b/stocks-update-notification-bot-dotnet/images/app.gif deleted file mode 100644 index 3cdcac8d..00000000 Binary files a/stocks-update-notification-bot-dotnet/images/app.gif and /dev/null differ diff --git a/stocks-update-notification-bot-dotnet/images/app.png b/stocks-update-notification-bot-dotnet/images/app.png new file mode 100644 index 00000000..7031ae8b Binary files /dev/null and b/stocks-update-notification-bot-dotnet/images/app.png differ diff --git a/stocks-update-notification-bot/README.md b/stocks-update-notification-bot/README.md index 216db7cd..f061fb27 100644 --- a/stocks-update-notification-bot/README.md +++ b/stocks-update-notification-bot/README.md @@ -17,7 +17,7 @@ Bots can be used to deliver pro-active messages into different Microsoft Teams c The Stocks Update Notification bot shows you how to request data on a pretermined schedule from a public API using API Key authentication and render that data using an Adaptive Card in different Microsoft Teams contexts. -![Stocks Update Notification Bot](assets/sampleDemo.gif) +![Stocks Update Notification Bot](assets/sampleDemo.png) ## This sample illustrates - How to launch and debug an app using "F5" using Teams Toolkit for Visual Studio. diff --git a/stocks-update-notification-bot/assets/sampleDemo.gif b/stocks-update-notification-bot/assets/sampleDemo.gif deleted file mode 100644 index 3cdcac8d..00000000 Binary files a/stocks-update-notification-bot/assets/sampleDemo.gif and /dev/null differ diff --git a/stocks-update-notification-bot/assets/sampleDemo.png b/stocks-update-notification-bot/assets/sampleDemo.png new file mode 100644 index 00000000..7031ae8b Binary files /dev/null and b/stocks-update-notification-bot/assets/sampleDemo.png differ diff --git a/team-central-dashboard/assets/sampleDemo.gif b/team-central-dashboard/assets/sampleDemo.gif index 6367fd8b..f96dc301 100644 Binary files a/team-central-dashboard/assets/sampleDemo.gif and b/team-central-dashboard/assets/sampleDemo.gif differ diff --git a/team-central-dashboard/assets/thumbnail.png b/team-central-dashboard/assets/thumbnail.png index c5dec6cd..5e3ac0fd 100644 Binary files a/team-central-dashboard/assets/thumbnail.png and b/team-central-dashboard/assets/thumbnail.png differ diff --git a/todo-list-SPFx/README.md b/todo-list-SPFx/README.md index f4b38a2e..8ed37ec1 100644 --- a/todo-list-SPFx/README.md +++ b/todo-list-SPFx/README.md @@ -56,7 +56,7 @@ Debug the app with Teams workbench ![debug](images/localdebug.png) 3. You should see the app running in your Teams. ![localdebug-preview](images/localdebug-preview.png) -### Deploy the app to Azure +### Deploy the app >Here are the instructions to run the sample in **Visual Studio Code**. You can also try to run the app using TeamsFx CLI tool, refer to [Try sample with TeamsFx CLI](cli.md) 1. Clone the repo to your local workspace or directly download the source code. diff --git a/todo-list-SPFx/assets/sampleDemo.gif b/todo-list-SPFx/assets/sampleDemo.gif index b3f92a02..8b6fdfa1 100644 Binary files a/todo-list-SPFx/assets/sampleDemo.gif and b/todo-list-SPFx/assets/sampleDemo.gif differ diff --git a/todo-list-SPFx/assets/thumbnail.png b/todo-list-SPFx/assets/thumbnail.png index 51d8dcd8..26f5ff64 100644 Binary files a/todo-list-SPFx/assets/thumbnail.png and b/todo-list-SPFx/assets/thumbnail.png differ diff --git a/todo-list-SPFx/images/Publish.png b/todo-list-SPFx/images/Publish.png index 9106aee8..8151c99c 100644 Binary files a/todo-list-SPFx/images/Publish.png and b/todo-list-SPFx/images/Publish.png differ diff --git a/todo-list-SPFx/images/TeamsAppAdminCenter.png b/todo-list-SPFx/images/TeamsAppAdminCenter.png index b0684d22..34b7426c 100644 Binary files a/todo-list-SPFx/images/TeamsAppAdminCenter.png and b/todo-list-SPFx/images/TeamsAppAdminCenter.png differ diff --git a/todo-list-SPFx/images/ToDoListCRUD.gif b/todo-list-SPFx/images/ToDoListCRUD.gif index a830d468..a4b9195f 100644 Binary files a/todo-list-SPFx/images/ToDoListCRUD.gif and b/todo-list-SPFx/images/ToDoListCRUD.gif differ diff --git a/todo-list-SPFx/images/addapp.png b/todo-list-SPFx/images/addapp.png index bc118518..b52d0706 100644 Binary files a/todo-list-SPFx/images/addapp.png and b/todo-list-SPFx/images/addapp.png differ diff --git a/todo-list-SPFx/images/addtoateam.png b/todo-list-SPFx/images/addtoateam.png index 3456b410..5d128faf 100644 Binary files a/todo-list-SPFx/images/addtoateam.png and b/todo-list-SPFx/images/addtoateam.png differ diff --git a/todo-list-SPFx/images/appdisplay.png b/todo-list-SPFx/images/appdisplay.png index 8d4f4136..68cebbaf 100644 Binary files a/todo-list-SPFx/images/appdisplay.png and b/todo-list-SPFx/images/appdisplay.png differ diff --git a/todo-list-SPFx/images/localdebug-preview.png b/todo-list-SPFx/images/localdebug-preview.png index fe82db5e..e337946f 100644 Binary files a/todo-list-SPFx/images/localdebug-preview.png and b/todo-list-SPFx/images/localdebug-preview.png differ diff --git a/todo-list-SPFx/images/localdebug.png b/todo-list-SPFx/images/localdebug.png index 8a3c4fef..e4f099dc 100644 Binary files a/todo-list-SPFx/images/localdebug.png and b/todo-list-SPFx/images/localdebug.png differ diff --git a/todo-list-with-Azure-backend-M365/images/todo-list-M365.gif b/todo-list-with-Azure-backend-M365/images/todo-list-M365.gif index 4e26b575..ac2b3f57 100644 Binary files a/todo-list-with-Azure-backend-M365/images/todo-list-M365.gif and b/todo-list-with-Azure-backend-M365/images/todo-list-M365.gif differ diff --git a/todo-list-with-Azure-backend/assets/sampleDemo.gif b/todo-list-with-Azure-backend/assets/sampleDemo.gif index 0c2c2645..3e812727 100644 Binary files a/todo-list-with-Azure-backend/assets/sampleDemo.gif and b/todo-list-with-Azure-backend/assets/sampleDemo.gif differ diff --git a/todo-list-with-Azure-backend/assets/thumbnail.png b/todo-list-with-Azure-backend/assets/thumbnail.png index 71b61a12..720d1897 100644 Binary files a/todo-list-with-Azure-backend/assets/thumbnail.png and b/todo-list-with-Azure-backend/assets/thumbnail.png differ