Skip to content

Commit

Permalink
feat: update dashboard samples to use TokenCredentialProvider (#981)
Browse files Browse the repository at this point in the history
* feat: update developer-assist-dashboard

* feat: update dashboard to use latest SDK apis
  • Loading branch information
yiqing-zhao authored Aug 8, 2023
1 parent 706fd1a commit f8f7e71
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 63 deletions.
70 changes: 53 additions & 17 deletions developer-assist-dashboard/api/callService/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import "isomorphic-fetch";

import { Context, HttpRequest } from "@azure/functions";
import { Client, ResponseType } from "@microsoft/microsoft-graph-client";
import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials";
import {
createMicrosoftGraphClientWithCredential,
OnBehalfOfCredentialAuthConfig,
OnBehalfOfUserCredential,
} from "@microsoft/teamsfx";
Expand All @@ -30,7 +30,6 @@ type TeamsfxContext = { [key: string]: any };
* 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:
* - TeamsFx().setSsoToken() - Construct teamsfx instance with the received SSO token and initialized configuration.
* - getUserInfo() - Get the user's information from the received SSO token.
* - createMicrosoftGraphClient() - Get a graph client to access user's Microsoft 365 data.
*
Expand Down Expand Up @@ -100,7 +99,12 @@ export default async function run(

try {
// Call the appropriate function based on the graphType and method.
const result = await handleRequest(oboCredential, serviceType, method, reqData);
const result = await handleRequest(
oboCredential,
serviceType,
method,
reqData
);
res.body = { ...res.body, ...result };
} catch (e) {
context.log.error(e);
Expand Down Expand Up @@ -162,7 +166,9 @@ async function getDevops(): Promise<any> {
const url = `https://dev.azure.com/${config.devopsOrgName}/${config.devopsProjectName}/_apis/wit/workitems?ids=1,2,3,4,5&api-version=7.0`;

// Encode the access token for authentication.
const auth = Buffer.from(`Basic:${config.devopsAccessToken}`).toString("base64");
const auth = Buffer.from(`Basic:${config.devopsAccessToken}`).toString(
"base64"
);

// Set the headers for the request.
const headers = {
Expand Down Expand Up @@ -240,12 +246,21 @@ async function createIssue(reqData: any) {
* @param oboCredential The OnBehalfOfUserCredential object for authentication.
* @returns An array of tasks, each containing the task ID, title, priority, percent complete, assigned users, and over-assigned users.
*/
async function getPlanner(oboCredential: OnBehalfOfUserCredential): Promise<any> {
// Create a Microsoft Graph client with the provided credentials and permissions.
const graphClient = createMicrosoftGraphClientWithCredential(oboCredential, [
"Tasks.ReadWrite",
"Group.ReadWrite.All",
]);
async function getPlanner(
oboCredential: OnBehalfOfUserCredential
): Promise<any> {
// Create an instance of the TokenCredentialAuthenticationProvider by passing the tokenCredential instance and options to the constructor
const authProvider = new TokenCredentialAuthenticationProvider(
oboCredential,
{
scopes: ["Tasks.ReadWrite", "Group.ReadWrite.All"],
}
);

// Initialize Graph client instance with authProvider
const graphClient = Client.initWithMiddleware({
authProvider: authProvider,
});

// Retrieve the top 8 tasks from the specified Planner plan.
const { value: tasksData } = await graphClient
Expand Down Expand Up @@ -282,11 +297,18 @@ async function createPlannerTask(
oboCredential: OnBehalfOfUserCredential,
reqData: any
): Promise<any> {
// Create a Microsoft Graph client with the provided credentials and permissions.
const graphClient: Client = createMicrosoftGraphClientWithCredential(oboCredential, [
"Tasks.ReadWrite",
"Group.ReadWrite.All",
]);
// Create an instance of the TokenCredentialAuthenticationProvider by passing the tokenCredential instance and options to the constructor
const authProvider = new TokenCredentialAuthenticationProvider(
oboCredential,
{
scopes: ["Tasks.ReadWrite", "Group.ReadWrite.All"],
}
);

// Initialize Graph client instance with authProvider
const graphClient = Client.initWithMiddleware({
authProvider: authProvider,
});

// Create a task object with the provided title and assignments.
const task = {
Expand All @@ -308,8 +330,22 @@ async function createPlannerTask(
* @param userId The ID of the user to retrieve information for.
* @returns An object containing the user's ID, display name, and avatar (if available).
*/
async function getUser(oboCredential: OnBehalfOfUserCredential, userId: string): Promise<any> {
const graphClient = createMicrosoftGraphClientWithCredential(oboCredential, ["User.Read.All"]);
async function getUser(
oboCredential: OnBehalfOfUserCredential,
userId: string
): Promise<any> {
// Create an instance of the TokenCredentialAuthenticationProvider by passing the tokenCredential instance and options to the constructor
const authProvider = new TokenCredentialAuthenticationProvider(
oboCredential,
{
scopes: ["User.Read.All"],
}
);

// Initialize Graph client instance with authProvider
const graphClient = Client.initWithMiddleware({
authProvider: authProvider,
});

// Send a GET request to retrieve the user's display name.
const { displayName } = await graphClient.api(`/users/${userId}`).get();
Expand Down
130 changes: 104 additions & 26 deletions team-central-dashboard/api/callGraph/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import "isomorphic-fetch";

import { Context, HttpRequest } from "@azure/functions";
import { Client } from "@microsoft/microsoft-graph-client";
import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials";
import {
createMicrosoftGraphClient,
createMicrosoftGraphClientWithCredential,
IdentityType,
AppCredential,
AppCredentialAuthConfig,
OnBehalfOfCredentialAuthConfig,
OnBehalfOfUserCredential,
TeamsFx,
} from "@microsoft/teamsfx";

import config from "../config";
Expand Down Expand Up @@ -44,7 +43,6 @@ enum FilesType {
* 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.
* - createMicrosoftGraphClientWithCredential() - Get a graph client to access user's Microsoft 365 data.
*
* The response contains multiple message blocks constructed into a JSON object, including:
* - An echo of the request body.
Expand Down Expand Up @@ -123,7 +121,12 @@ export default async function run(

try {
// Call the appropriate function based on the graphType and method.
const result = await handleRequest(oboCredential, graphType, method, reqData);
const result = await handleRequest(
oboCredential,
graphType,
method,
reqData
);
res.body = { ...res.body, ...result };
} catch (e) {
context.log.error(e);
Expand Down Expand Up @@ -193,7 +196,18 @@ async function handleRequest(
* @returns A promise that resolves with an array of calendar events.
*/
async function getCalendarEvents(oboCredential: OnBehalfOfUserCredential) {
const graphClient = createMicrosoftGraphClientWithCredential(oboCredential, ["Calendars.Read"]);
// Create an instance of the TokenCredentialAuthenticationProvider by passing the tokenCredential instance and options to the constructor
const authProvider = new TokenCredentialAuthenticationProvider(
oboCredential,
{
scopes: ["Calendars.Read"],
}
);

// Initialize Graph client instance with authProvider
const graphClient = Client.initWithMiddleware({
authProvider: authProvider,
});

// Set the end of the day to 23:59:59.999
const endOfDay = new Date();
Expand Down Expand Up @@ -227,8 +241,18 @@ async function getCalendarEvents(oboCredential: OnBehalfOfUserCredential) {
* @returns {Promise<TaskModel[]>} - A promise that resolves with an array of tasks.
*/
async function getTasksInfo(oboCredential: OnBehalfOfUserCredential) {
// Create a Microsoft Graph client with the provided credential and required permissions
const graphClient = createMicrosoftGraphClientWithCredential(oboCredential, ["Tasks.ReadWrite"]);
// Create an instance of the TokenCredentialAuthenticationProvider by passing the tokenCredential instance and options to the constructor
const authProvider = new TokenCredentialAuthenticationProvider(
oboCredential,
{
scopes: ["Tasks.ReadWrite"],
}
);

// Initialize Graph client instance with authProvider
const graphClient = Client.initWithMiddleware({
authProvider: authProvider,
});

// Get the user's to-do lists
const { value: tasklists } = await graphClient.api("/me/todo/lists").get();
Expand All @@ -238,7 +262,9 @@ async function getTasksInfo(oboCredential: OnBehalfOfUserCredential) {

// Get the tasks from the to-do list that are not completed and limit the results to 3
const { value: tasksInfo } = await graphClient
.api(`/me/todo/lists/${todoTaskListId}/tasks?$filter=status ne 'completed'&$top=3`)
.api(
`/me/todo/lists/${todoTaskListId}/tasks?$filter=status ne 'completed'&$top=3`
)
.get();

// Map the tasks to a simpler format
Expand All @@ -260,11 +286,22 @@ async function getTasksInfo(oboCredential: OnBehalfOfUserCredential) {
* @param {any} reqData - The request data containing the task title.
* @returns A promise that resolves with an array of tasks.
*/
async function createTask(oboCredential: OnBehalfOfUserCredential, reqData: any) {
const graphClient = createMicrosoftGraphClientWithCredential(oboCredential, [
"Tasks.ReadWrite",
"User.Read",
]);
async function createTask(
oboCredential: OnBehalfOfUserCredential,
reqData: any
) {
// Create an instance of the TokenCredentialAuthenticationProvider by passing the tokenCredential instance and options to the constructor
const authProvider = new TokenCredentialAuthenticationProvider(
oboCredential,
{
scopes: ["Tasks.ReadWrite", "User.Read"],
}
);

// Initialize Graph client instance with authProvider
const graphClient = Client.initWithMiddleware({
authProvider: authProvider,
});

// Get the user's to-do lists
const { value: tasklists } = await graphClient.api("/me/todo/lists").get();
Expand All @@ -278,14 +315,22 @@ async function createTask(oboCredential: OnBehalfOfUserCredential, reqData: any)
.post({ title: reqData.taskTitle });

// Import the TeamsFx SDK and create a new instance for the app identity
let teamsfxApp = new TeamsFx(IdentityType.App);
const authConfig: AppCredentialAuthConfig = {
authorityHost: config.authorityHost,
clientId: config.clientId,
tenantId: config.tenantId,
clientSecret: config.clientSecret,
};
const appCredential: AppCredential = new AppCredential(authConfig);

// Send an activity notification to the user's Teams activity feed
sendActivityNotification(teamsfxApp, graphClient);
sendActivityNotification(appCredential, graphClient);

// Get the tasks from the to-do list that are not completed and limit the results to 3
const { value: tasksInfo } = await graphClient
.api(`/me/todo/lists/${todoTaskListId}/tasks?$filter=status ne 'completed'&$top=3`)
.api(
`/me/todo/lists/${todoTaskListId}/tasks?$filter=status ne 'completed'&$top=3`
)
.get();

// Map the tasks to a simpler format
Expand All @@ -306,10 +351,21 @@ async function createTask(oboCredential: OnBehalfOfUserCredential, reqData: any)
* @param {TeamsFx} teamsfxApp - The TeamsFx instance for the app identity.
* @param {Client} graphClient - The Microsoft Graph client.
*/
async function sendActivityNotification(teamsfxApp: TeamsFx, graphClient: Client) {
async function sendActivityNotification(
appCredential: AppCredential,
graphClient: Client
) {
try {
// Create a Microsoft Graph client using the app identity and the default scope
const appGraphClient = createMicrosoftGraphClient(teamsfxApp, [".default"]);
// 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"],
}
);
let appGraphClient: Client = Client.initWithMiddleware({
authProvider: authProvider,
});

// Get user ID
const userProfile = await graphClient.api("/me").get();
Expand Down Expand Up @@ -351,11 +407,24 @@ async function sendActivityNotification(teamsfxApp: TeamsFx, graphClient: Client
* @returns A promise that resolves with an array of files.
*/
async function getFiles(oboCredential: OnBehalfOfUserCredential) {
const graphClient = createMicrosoftGraphClientWithCredential(oboCredential, ["Calendars.Read"]);
// Create an instance of the TokenCredentialAuthenticationProvider by passing the tokenCredential instance and options to the constructor
const authProvider = new TokenCredentialAuthenticationProvider(
oboCredential,
{
scopes: ["Calendars.Read"],
}
);

// Initialize Graph client instance with authProvider
const graphClient = Client.initWithMiddleware({
authProvider: authProvider,
});

// Get the user's recently accessed files
const { value: driveInfo } = await graphClient
.api("/me/drive/recent?$top=5&$select=id,name,webUrl,createdBy,lastModifiedBy,remoteItem")
.api(
"/me/drive/recent?$top=5&$select=id,name,webUrl,createdBy,lastModifiedBy,remoteItem"
)
.get();

// Map the files to a simpler format
Expand Down Expand Up @@ -401,7 +470,12 @@ async function getFiles(oboCredential: OnBehalfOfUserCredential) {
* @param {Object} param0 - The file information.
* @returns {string} - The Teams URL.
*/
function generateTeamsUrl({ webUrl, mimeType, webDavUrl, sharepointIds }): string {
function generateTeamsUrl({
webUrl,
mimeType,
webDavUrl,
sharepointIds,
}): string {
let url = "https://teams.microsoft.com/l/file/";

// Get the file ID from the web URL
Expand All @@ -426,7 +500,9 @@ function generateTeamsUrl({ webUrl, mimeType, webDavUrl, sharepointIds }): strin
fileTypeString = "vsd";
break;
default:
fileTypeString = mimeType.substring(mimeType.indexOf("application/" + 12));
fileTypeString = mimeType.substring(
mimeType.indexOf("application/" + 12)
);
break;
}
url += "fileType=" + fileTypeString;
Expand All @@ -436,7 +512,9 @@ function generateTeamsUrl({ webUrl, mimeType, webDavUrl, sharepointIds }): strin
url += "&objectUrl=" + encodedObjectURL;

// Encode the base URL and add it to the URL
const encodedBaseUrl = sharepointIds.replace(/:/g, "%3A").replace(/\//g, "%2F");
const encodedBaseUrl = sharepointIds
.replace(/:/g, "%3A")
.replace(/\//g, "%2F");
url += "&baseUrl=" + encodedBaseUrl;

return url;
Expand Down
41 changes: 21 additions & 20 deletions team-central-dashboard/api/package.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
{
"name": "teamsfx-template-api",
"version": "1.0.0",
"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",
"isomorphic-fetch": "^3.0.0"
},
"devDependencies": {
"env-cmd": "^10.1.0",
"typescript": "^4.4.4"
}
"name": "teamsfx-template-api",
"version": "1.0.0",
"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"
}
}

0 comments on commit f8f7e71

Please sign in to comment.