Microsoft Teams Toolkit supported Dashboard scenario since V4.2.0, welcome to experience it!
This is a Teams dashboard app that uses the Fluent UI to display multiple cards in Teams Tab.
This app also supported teams different themes, including dark theme and high contrast theme.
| Dark theme | High contrast theme |
|---|---|
![]() |
![]() |
- NodeJS, fully tested on NodeJS 14, 16
- A Microsoft 365 account. If you do not have Microsoft 365 account, apply one from Microsoft 365 developer program
- Teams Toolkit Visual Studio Code Extension or TeamsFx CLI
Run your app with local debugging by pressing F5 in VSCode. Select Debug (Edge) or Debug (Chrome).
Congratulations! You are running an application that can now show a dashboard in Teams.
This section walks through the generated code. The project folder contains the following:
| Folder | Contents |
|---|---|
.fx |
Project level settings, configurations, and environment information |
.vscode |
VSCode files for local debug |
tabs |
The source code for the dashboard tab Teams application |
templates |
Templates for the Teams application manifest and for provisioning Azure resources |
The core dashboard implementation is in tabs folder.
The following files provide the business logic for the dashboard tab. These files can be updated to fit your business logic requirements. The default implementation provides a starting point to help you get started.
| File | Contents |
|---|---|
src/models/listModel.tsx |
Data model for the list widget |
src/services/listService.tsx |
A data retrive implementation for the list widget |
src/views/dashboards/SampleDashboard.tsx |
A sample dashboard layout implementation |
src/views/lib/Dashboard.styles.ts |
The dashbaord style file |
src/views/lib/Dashboard.tsx |
An base class that defines the dashboard |
src/views/lib/Widget.styles.ts |
The widgt style file |
src/views/lib/Widget.tsx |
An abstract class that defines the widget |
src/views/styles/ListWidget.styles.ts |
The list widget style file |
src/views/widgets/ChartWidget.tsx |
A widget implementation that can display a chart |
src/views/widgets/ListWidget.tsx |
A widget implementation that can display a list |
The following files are project-related files. You generally will not need to customize these files.
| File | Contents |
|---|---|
src/index.tsx |
Application entry point |
src/App.tsx |
Application route |
src/internal/addNewScopes.ts |
Implementation of new scopes add |
src/internal/context.ts |
TeamsFx Context |
src/internal/login.ts |
Implementation of login |
src/internal/singletonContext.ts |
Implementation of the TeamsFx instance singleton |
You can use the following steps to add a new widget to the dashboard:
- Step 1: Define a data model
- Step 2: Create a data retrive service
- Step 3: Create a widget file
- Step 4: Add the widget to the dashboard
Define a data model based on the business scenario, and put it in tabs/src/models folder. The widget model defined according to the data you want to display in the widget. Here's a sample data model:
export interface SampleModel {
content: string;
}Simplely, you can create a service that returns dummy data. We recommend that you put data files in the tabs/src/data folder, and put data retrive services in the tabs/src/services folder.
Here's a sample json file that contains dummy data:
{
"content": "Hello world!"
}Here's a dummy data retrive service:
import { SampleModel } from "../models/sampleModel";
import SampleData from "../data/SampleData.json";
export const getSampleData = (): SampleModel => SampleData;Note: You can also implement a service to retrieve data from the backend service or from the Microsoft Graph API.
Create a widget file in tabs/src/views/widgets folder. Extend the Widget class. The following table lists the methods that you can override to customize your widget.
| Methods | Function |
|---|---|
getData() |
This method is used to get the data for the widget. You can implement it to get data from the backend service or from the Microsoft Graph API |
headerContent() |
Customize the content of the widget header |
bodyContent() |
Customize the content of the widget body |
footerContent() |
Customize the content of the widget footer |
All methods are optional. If you do not override any method, the default widget layout will be used.
Here's a sample widget implementation:
import { Button, Text } from "@fluentui/react-components";
import { Widget } from "../lib/Widget";
import { SampleModel } from "../../models/sampleModel";
import { getSampleData } from "../../services/sampleService";
export class SampleWidget extends Widget<SampleModel> {
async getData(): Promise<SampleModel> {
return getSampleData();
}
headerContent(): JSX.Element | undefined {
return <Text>Sample Widget</Text>;
}
bodyContent(): JSX.Element | undefined {
return <div>{this.state.data?.content}</div>;
}
footerContent(): JSX.Element | undefined {
return (
<Button
appearance="primary"
size="medium"
style={{ width: "fit-content" }}
onClick={() => {}}
>
View Details
</Button>
);
}
}- Go to
tabs/src/views/dashboards/SampleDashboard.tsx, if you want create a new dashboard, please refer to How to add a new dashboard. - Update your
dashboardLayout()method to add the widget to the dashboard:
protected dashboardLayout(): void | JSX.Element {
return (
<>
<ListWidget />
<ChartWidget />
<SampleWidget />
</>
);
}Note: If you want put your widget in a column, you can use the
oneColumn()method to define the column layout. Here is an example:
protected dashboardLayout(): void | JSX.Element {
return (
<>
<ListWidget />
<div style={oneColumn()}>
<ChartWidget />
<SampleWidget />
</div>
</>
);
}You can use the following steps to add a new dashboard layout:
- Step 1: Create a dashboard class
- Step 2: Override methods to customize dashboard layout
- Step 3: Add a route for the new dashboard
- Step 4: Modify manifest to add a new dashboard tab
Create a file with the extension .tsx for your dashboard in the tabs/src/views/dashboards directory. For example, YourDashboard.tsx. Then, create a class that extends the Dashboard class.
export default class YourDashboard extends Dashboard {}Dashboard class provides some methods that you can override to customize the dashboard layout. The following table lists the methods that you can override.
| Methods | Function |
|---|---|
rowHeights() |
Customize the height of each row of the dashboard |
columnWidths() |
Customize how many columns the dashboard has at most and the width of each column |
dashboardLayout() |
Define widgets layout |
Here is an example to customize the dashboard layout.
export default class YourDashboard extends Dashboard {
protected rowHeights(): string | undefined {
return "500px";
}
protected columnWidths(): string | undefined {
return "4fr 6fr";
}
protected dashboardLayout(): void | JSX.Element {
return (
<>
<SampleWidget />
<div style={oneColumn("6fr 4fr")}>
<SampleWidget />
<SampleWidget />
</div>
</>
);
}
}Note: All methods are optional. If you do not override any method, the default dashboard layout will be used.
Open the tabs/src/App.tsx file, and add a route for the new dashboard. Here is an example:
import YourDashboard from "./views/dashboards/YourDashboard";
export default function App() {
...
<Route exact path="/yourdashboard" component={YourDashboard} />
...
}Open the templates/appPackage/manifest.template.json file, and add a new dashboard tab under the staticTabs. Here is an example:
{
"name": "Your Dashboard",
"entityId": "yourdashboard",
"contentUrl": "{{state.fx-resource-frontend-hosting.endpoint}}{{state.fx-resource-frontend-hosting.indexPath}}/yourdashboard",
"websiteUrl": "{{state.fx-resource-frontend-hosting.endpoint}}{{state.fx-resource-frontend-hosting.indexPath}}/yourdashboard",
"scopes": ["personal"]
}Before you add your logic of calling a Graph API, you should enable your dashboard project to use SSO. It is convenient to add SSO related files by using Teams Toolkit. Refer to the following 2 steps to add SSO.
-
Step 1: Click
Teams Toolkitin the side bar > ClickAdd featuresinDEVELOPMENT.
-
Step 2: Choose
Single Sign-Onto add.
-
Step 3: Move
auth-start.htmlandauth-end.htmlinauth/tab/publicfolder totabs/public/. These two HTML files are used for auth redirects. -
Step 4: Move
ssofolder underauth/tabtotabs/src/sso/.
Now you have already added SSO files to your project, and you can call Graph APIs. There are two types of Graph APIs, one will be called from the front-end(most of APIs, use delegated permissions), the other will be called from the back-end(sendActivityNotification, e.g., use application permissions). You can refer to this tutorial to check permission types of the Graph APIs you want to call.
If you want to call a Graph API from the front-end tab, you can refer to the following steps.
- Step 1: Consent delegated permissions first
- Step 2: Create a graph client by adding the scope related to the Graph API you want to call
- Step 3: Call the Graph API, and parse the response into a certain model, which will be used by front-end
You can call addNewScope(scopes: string[]) to consent the scopes of permissions you want to add. And the consented status will be preserved in a global context FxContext.
You can refer to the Graph API V1.0 to get the scope name of the permission related to the Graph API you want to call.
You can refer to the following code snippet:
let teamsfx: TeamsFx;
teamsfx = FxContext.getInstance().getTeamsFx();
const graphClient: Client = createMicrosoftGraphClient(teamsfx, scope);Step 3: Call the Graph API, and parse the response into a certain model, which will be used by front-end
You can refer to the following code snippet:
try {
const graphApiResult = await graphClient.api("<GRAPH_API_PATH>").get();
// Parse the graphApiResult into a Model you defined, used by the front-end.
} catch (e) {}If you want to call a Graph API from the back-end, you can refer to the following steps. In this tutorial, we use sendActivityNotification API for example.
- Step 1: Consent application permissions first
- Step 2: Add an Azure Function
- Step 3: Get the
installation idof your Dashboard app - Step 4: Add your logic in Azure Function
- Step 5: Edit manifest file
- Step 6: Call the Azure Function from the front-end
Go to Azure portal > Click Azure Active Directory > Click App registrations in the side bar > Click your Dashboard app > Click API permissions in the side bar > Click +Add a permission > Choose Microsoft Graph > Choose Application permissions > Find the permissions you need > Click Add permissions button in the bottom > Click ✔Grant admin consent for XXX and then click Yes button to finish the admin consent
In the VS Code side bar, click Add features in Teams Toolkit > Choose Azure functions > Enter the function name
Go Graph Explorer, and use the following api path to get a response.
https://graph.microsoft.com/v1.0/users/{user-id | user-principal-name}/teamwork/installedApps?$expand=teamsAppDefinition
In the response, you should find the information of your Dashboard app, and then record the id of it as installationId, which will be used in step 4.
In the index.ts under the folder named in step 2, you can add the following code snippet to call sendActivityNotification
try {
// do sth here, to call activity notification api
//
const graphClient_userId: Client = await createMicrosoftGraphClient(teamsfx, [
"User.Read",
]);
const userIdRes = await graphClient_userId.api("/me").get();
const userId = userIdRes["id"];
// get installationId
const installationId =
"ZmYxMGY2MjgtYjJjMC00MzRmLTgzZmItNmY3MGZmZWEzNmFkIyMyM2NhNWVlMy1iYWVlLTRiMjItYTA0OC03YjkzZjk0MDRkMTE=";
let postbody = {
topic: {
source: "entityUrl",
value:
"https://graph.microsoft.com/v1.0/users/" +
userId +
"/teamwork/installedApps/" +
installationId,
},
activityType: "taskCreated",
previewText: {
content: "New Task Created",
},
templateParameters: [
{
name: "taskId",
value: "12322",
},
],
};
let teamsfx_app: TeamsFx;
teamsfx_app = new TeamsFx(IdentityType.App);
const graphClient: Client = createMicrosoftGraphClient(teamsfx_app, [
".default",
]);
await graphClient
.api("users/" + userId + "/teamwork/sendActivityNotification")
.post(postbody);
} catch (e) {
console.log(e);
}In the templates\appPackage\manifest.template.json, you should add the following properties, which are align with properties in postbody in Step 4.
"activities": {
"activityTypes": [
{
"type": "taskCreated",
"description": "Task Created",
"templateText": "{actor} created task {taskId}"
}
]
}Call the Azure Function from the front-end. You can refer to the following code snippet to call the Azure Function.
const functionName = process.env.REACT_APP_FUNC_NAME || "myFunc";
async function callFunction(teamsfx?: TeamsFx) {
if (!teamsfx) {
throw new Error("TeamsFx SDK is not initialized.");
}
try {
const credential = teamsfx.getCredential();
const apiBaseUrl = teamsfx.getConfig("apiEndpoint") + "/api/";
// createApiClient(...) creates an Axios instance which uses BearerTokenAuthProvider to inject token to request header
const apiClient = createApiClient(
apiBaseUrl,
new BearerTokenAuthProvider(
async () => (await credential.getToken(""))!.token
)
);
const response = await apiClient.get(functionName);
return response.data;
} catch (e) {}
}Refer to this sample for some helps. And you can read this doc for more details.




