SPFx Web Part. Получение информации о присутствии из Microsoft Teams

Microsoft Teams был выпущен в 2017 году и потребовалось около трех лет, чтобы мы получили API, позволяющий получить информацию о присутствии. Приходилось использовать обходные пути. Но сейчас у нас есть методы REST API для получения онлайн статуса пользователя в Microsoft Teams.

В этом посте я покажу как в веб-части SPFx получить информацию о присутствии пользователя из Microsoft Teams.

Создание новой веб-части

Откройте терминал и выполните команду для создания новой SPFx веб-части:

yo @microsoft/sharepoint

Создание новой веб-части

Создание новой веб-части

Пакеты

Для получение информации о присутствии необходимо предоставить идентификатор пользователя из AAD. Чтобы получить идентификатор AAD необходимо знать логин пользователя. И вот для получение логина мы будем использовать spfx-controls react. Выполните команду, чтобы добавить этот пакет в проект:

npm install @pnp/spfx-controls-react --save --save-exact

Это единственный дополнительный npm-пакет, который понадобится.

Когда Yeoman закончит свою работу и пакет будет добавлен в проект выполните code . , чтобы запустить Visual Studio Code или любой другой редактор, который Вы предпочитаете.

Web Part до модификации

Web Part после модификации

Отмеченные файлы будут изменены.

Обзор решения

Решение не сильно красивое в угоду простоте кода.

UI

Веб-часть содержит три элемента: Контрол выбора пользователя для поиска пользователей в тенанте, Кнопка для инициализации процесса получения статуса и набор текстовых элементов для отображения информации о статусе из Microsoft Teams.

Веб-часть отображения статуса пользователя в Microsoft Teams

Веб-часть отображения статуса пользователя в Microsoft Teams

Процесс

Процесс веб-части состоит из трех шагов:

  1. Выбор пользователя с помощью контрола People Picker (People Picker возвращает логин пользователя)
  2. Передать логин пользователя в Microsoft Graph, чтобы получить AAD идентификатор пользователя
  3. Передать AAD идентификатор пользователя в Microsoft Graph, чтобы получить статус присутствия

Код

Код веб-части содержит только минимально необходимые модификации для демонстрации новых возможностей.

Модель данных

Прежде всего нам надо определить объект статуса присутствия. Создайте папку модель model в папке src, затем создайте файл IPresence.ts со следующим содержимым:

export interface IPresence {
  Availability: string;
  Activity: string;
}

Далее необходимо расширить объекты свойства и состояние компонента веб-части для хранения информации о присутствии и контекста веб-части (логин пользователя, ID пользователя, статус присутствия). Откройте файл src\components\IUserPresenceProps.ts и замените его содержимое на следующее:

import { WebPartContext } from "@microsoft/sp-webpart-base";
import { IPresence } from "../../../model/IPresence";

/**
 * Свойства компонента
 */
export interface IUserPresenceProps {
  context: WebPartContext;
}

/**
 * Состояние компонента
 */
export interface IUserPresenceState{
  userUPN?: string;
  userId?: string;
  presence?: IPresence;
}

Клиент Microsoft Graph

Веб-часть использует Microsoft Graph как источник данных о пользователях и информации о присутствия. Создайте папку services в папке src и создайте в ней файл GraphService.ts:

import { WebPartContext } from "@microsoft/sp-webpart-base";
import { MSGraphClient } from "@microsoft/sp-http";
import { IPresence } from '../model/IPresence';

export default class GraphService {

    private context: WebPartContext;

    constructor(private _context: WebPartContext) {
        this.context = _context;
    }

    /**
     * Получение информации о присутствии
     * @param userId Идентификатор пользователя в AAD
     */
    public getPresence(userId: string): Promise<IPresence> {
        return new Promise<IPresence>((resolve, reject) => {
            this.context.msGraphClientFactory
                .getClient() // Инициализация клиента Microsoft Graph
                .then((client: MSGraphClient): Promise<IPresence> => {
                    return client
                        .api(`users/${userId}/presence`) //Получение статуса
                        .version("beta") // Бета-версия
                        .get((err, res) => {
                            if (err) {
                                reject(err);
                                return;
                            }
                            // Разрешаем статус
                            resolve({
                                Availability: res.availability,
                                Activity: res.activity,
                            });
                        });
                });
        });
    }

    public getUserId(userUPN: string): Promise<string> {
        return new Promise<string>((resolve, reject) => {
            this.context.msGraphClientFactory
                .getClient() // Инициализация клиента Microsoft Graph
                .then((client: MSGraphClient): Promise<IPresence> => {
                    return client
                        .api(`users/${userUPN}`) //Получение статуса
                        .version("beta") // Бета-версия
                        .select("id") // Получаем только ID пользователя
                        .get((err, res) => {
                            if (err) {
                                reject(err);
                                return;
                            }
                            // Разрешаем ИД пользователя
                            resolve(res.id);
                        });
                });
        });
    }
}

В клиенте определены два метода:

  • getUserId принимает логин пользователя (user@domain.com) и возвращает идентификатор пользователя в Azure AD
  • getPresence принимает идентификатор пользователя и возвращает статус присутствия

Компонент

Откройте файл src\webparts\userPresence\userPresence.tsx и замените его содержимое следующим:

import * as React from 'react';
import styles from './UserPresence.module.scss';
import { IUserPresenceProps, IUserPresenceState } from './IUserPresenceProps';
import { PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker";
import { Button } from 'office-ui-fabric-react';
import GraphService from '../../../services/GraphService';

export default class UserPresence extends React.Component<IUserPresenceProps, IUserPresenceState> {
  constructor(props: IUserPresenceProps) {
    super(props);
    this.state = {};

    // Связывание обработчиков событий
    this._buttonClick = this._buttonClick.bind(this);
    this._getPeoplePickerItems = this._getPeoplePickerItems.bind(this);

    // Инициализация клиента Microsoft Graph
    this._graphService = new GraphService(this.props.context);
  }

  private _graphService: GraphService

  public render(): React.ReactElement<IUserPresenceProps> {
    return (
      <div className={styles.userPresence}>
        {/* People Picker */}
        <div className={styles.container}>
          <PeoplePicker context={this.props.context} titleText="Step 1. Find user"
            selectedItems={this._getPeoplePickerItems} principalTypes={[PrincipalType.User]} />
        </div>
        {/* / People Picker */}

        {/* Кнопка */}
        <div className={styles.container}>
          <Button text="Step 2. Get presence" className={styles.button}
            disabled={!this.state.userUPN} onClick={this._buttonClick} />
        </div>
        {/* / Кнопка */}

        {/* Состояние */}
        <div className={styles.container}>
          <label>useUPN:</label> {this.state.userUPN}<br />
          <label>userId:</label> {this.state.userId}<br />
          <label>Presence.Activity:</label> {this.state.presence ? this.state.presence.Activity : ""}<br />
          <label>Presence.Availability:</label> {this.state.presence ? this.state.presence.Availability : ""}
        </div>
        {/* / Состояние */}
      </div>
    );
  }

  private _buttonClick() {
    this._graphService.getUserId(this.state.userUPN)
      .then(userId => {

        // Обновляем Id пользователя
        this.setState({
          userId: userId
        });

        this._graphService.getPresence(this.state.userId)
          .then(presence => {

            // Обновляем статус присутствия
            this.setState({ presence: presence });
          });
      });
  }

  private _getPeoplePickerItems(items: any[]): void {

    if (items.length != 1) {
      return;
    }

    // Обновляем логин
    this.setState({
      userUPN: items[0].secondaryText
    });
  }
}

Изменения в компоненте:

  1. Добавлен объект состояния
  2. Добавлен конструктор, принимающий WebPartContext вместо стандартного свойства description
  3. Изменен интерфейс для отображения элементов (пикер, кнопка, информация)
  4. Добавлены два обработчика: для пикера и для кнопки.

Веб-часть

Последний файл, который необходимо изменить - это src\webparts\userPresence\UserPresenceWebPart.ts. Откройте его и обновите его содержимое:

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import UserPresence from './components/UserPresence';
import { IUserPresenceProps } from './components/IUserPresenceProps';

export default class UserPresenceWebPart extends BaseClientSideWebPart <{}> {
  public render(): void {
    const element: React.ReactElement<IUserPresenceProps> = React.createElement(
      UserPresence,
      {
        context: this.context // WebPartContext для инициализации клиента Microsoft Graph
      }
    );
    ReactDom.render(element, this.domElement);
  }

  protected onDispose(): void {
    ReactDom.unmountComponentAtNode(this.domElement);
  }

  protected get dataVersion(): Version {
    return Version.parse('1.0');
  }
}

Итоги

Что было сделано:

  1. Добавлен пакет spfx-controls-react для использования контрола People Picker
  2. Создан интерфейс IPresence для определения статуса присутствия
  3. Создан IUserPresenceState для хранения состояния веб-части (выбранный пользователь, id пользователя, статус присутствия)
  4. Изменен IUserPresenceProps для передачи объекта WebPartContext из веб-части в дочерний компонент
  5. Создан GraphService для работы с Microsoft Graph
  6. Изменен компонент UserPresence для отображения элементов (пикер, кнопка, информация)
  7. Изменен UserPresenceWebPart для передачи контекста в компонент UserPresence

Исходные коды веб-части

Исходные коды веб-части

Просмотр веб-части

Выполните комнады в терминале для запуска веб-части:

npm run --nobrowser

откройте SharePoint Online workbench:

https://[tenant].sharepoint.com/_layouts/15/workbench.aspx

и добавьте веб-часть на страницу

Статус пользователя из Teams в SPFx Web Part

Статус пользователя из Teams в SPFx Web Part

Права

Для получение статуса пользователя в Microsoft Teams с помощью Microsoft Graph необходимо предоставить права Presence.Read (минимальные привилегии) или Presence.Read.All (максимальные привилегии) для веб-части.

Исходные коды

Исходные коды опубликованы на GitHub: https://github.com/vzhukov/spfx-teams-presence.

Виталий Жуков

Виталий Жуков

SharePoint архитектор, разработчик, тренер, Microsoft MVP (Office Development). Более 15 лет опыта работы с SharePoint, Dynamics CRM, Office 365, и другими продуктами и сервисами Microsoft.