Graph + SPFx + Document Cards

I am trying to get the documents from drive and display it in Office Fabric UI Control Document cards using SPFx with react and graph API.

Agenda
1. How can call Graph API from SPFx web part?
2. How can get recent documents using graph?
3. How can display documents in Document Cards?
4. How can approve permission of API from API management in admin panel?

Let’s start the code.

Step 1 : I am assuming that SPFx development environment is already set up. Click here to check how can set up environment for SPFx development environment.
Step 2 : I am not describe how can create a new project in spfx using node js. I assume that it is already created a fresh web part.
Click here to check how can create a fresh web part in spfx .
Step 3 : Open project in visual studio code and structure like as below.

Open SPFx project in visual studio code

Step 3 : Configure the API permissions requests
To consume Microsoft Graph or any other third-party REST API, you need to explicitly declare the permission requirements from an OAuth perspective in the manifest of your solution.
We can configure the api permission request using the webApiPermissionRequests  properties in package-solution.json under the config folder in solution in Sharepoint framework v1.4.1 or later.

{
  "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
  "solution": {
    "name": "spfxapiscopestutorial-client-side-solution",
    "id": "35c4764c-5f2c-48d3-8a86-82bae4fe6fb8",
    "version": "1.0.0.0",
    "includeClientSideAssets": true,
    "isDomainIsolated": false,
    "webApiPermissionRequests": [
      {
        "resource": "Microsoft Graph",
        "scope": "User.ReadBasic.All"
      },
      {
        "resource": "Microsoft Graph",
        "scope": "Sites.ReadWrite.All"
      },
      {
        "resource": "Microsoft Graph",
        "scope": "User.ReadWrite"
      }
    ]
  },
  "paths": {
    "zippedPackage": "solution/spfxapiscopestutorial.sppkg"
  }
}

Step 4 : Create some ts file in solution.
Create IGraphConsumerProps.ts, file in components folder in solution.

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

export interface IGraphConsumerProps {
  clientMode: ClientMode;
  context: WebPartContext;
}

Create IUser.ts, file in components folder in solution.

export interface IUser {
  email: string;
  displayName: string;
}

Create IMyRecentDocument.ts, file in components folder in solution.

import { IUser } from "./IUser";
export interface IMyRecentDocument {
  id: string;
  name: string;
  webUrl?: string;
  profileImageSrc: string;
  user: IUser;
}

Create TestImages.ts, file in components folder in solution.

const baseProductionCdnUrl =
  "https://static2.sharepointonline.com/files/fabric/office-ui-fabric-react-assets/";

export const TestImages = {
  choiceGroupBarUnselected:
    baseProductionCdnUrl + "choicegroup-bar-unselected.png",
  choiceGroupBarSelected: baseProductionCdnUrl + "choicegroup-bar-selected.png",
  choiceGroupPieUnselected:
    baseProductionCdnUrl + "choicegroup-pie-unselected.png",
  choiceGroupPieSelected: baseProductionCdnUrl + "choicegroup-pie-selected.png",
  documentPreview: baseProductionCdnUrl + "document-preview.png",
  documentPreviewTwo: baseProductionCdnUrl + "document-preview2.png",
  documentPreviewThree: baseProductionCdnUrl + "document-preview3.png",
  iconOne: baseProductionCdnUrl + "icon-one.png",
  iconPpt: baseProductionCdnUrl + "icon-ppt.png",
  personaFemale: baseProductionCdnUrl + "persona-female.png",
  personaMale: baseProductionCdnUrl + "persona-male.png"
};

Step 5 : Add Office Fabric Ui React component in solution using below command. Click Here
Open terminal in visual studio code using ctrl+shift+`

npm i office-ui-fabric-react

Step 6: Open <Webpartname>.tsx file under components folder in solution.
Add below code in tsx file. This all statements put before default class only.

import * as React from "react";
import styles from "./MyRecentDocument.module.scss";
import { IMyRecentDocumentProps } from "./IMyRecentDocumentProps";
import { escape } from "@microsoft/sp-lodash-subset";
import { ClientMode } from "./ClientMode";
import { autobind, css } from "@uifabric/utilities";
import { IGraphConsumerProps } from "./IGraphConsumerProps";
import { MSGraphClient, MSGraphClientFactory } from "@microsoft/sp-http";
import { TestImages } from "./TestImages";

import { IMyRecentDocument } from "./IMyrecentDocument";

export interface IMyRecentDocumentState {
  myRecentDocuments: IMyRecentDocument[];
  loading: boolean;
  error: string;
}

import {
  DocumentCard,
  DocumentCardActivity,
  DocumentCardPreview,
  DocumentCardTitle,
  IDocumentCardPreviewProps,
  DocumentCardType,
  Spinner
} from "office-ui-fabric-react";
import { ImageFit } from "office-ui-fabric-react/lib/Image";
import { IUser } from "./IUser";

Replace Code of default class extends with below code in tsx file.

export default class MyRecentDocument extends React.Component<
  IGraphConsumerProps,
  IMyRecentDocumentState
> {
  constructor(props: IGraphConsumerProps, state: IMyRecentDocumentState) {
    super(props);
    this.state = {
      myRecentDocuments: [] as IMyRecentDocument[],
      loading: true,
      error: null
    };
  }
  public componentDidMount(): void {
    // We will add code later
  }
  public render(): JSX.Element {
   // We will add code later
  }
}

Create a private method in to the default class. Copy below code in your solution.

private _loadDocuments(): void {
    this.props.context.msGraphClientFactory.getClient().then(
      (client: MSGraphClient): void => {
        client
          .api("/me/drive/recent")
          .version("v1.0")
          .get()
          .then((response: any) => {
            const myRecentDocuments: IMyRecentDocument[] = [];
            if (response.value.length > 0) {
              for (let index = 0; index < response.value.length; index++) {
                var t = response.value[index];
                myRecentDocuments.push({
                  name: response.value[index].name,
                  id: response.value[index].id,
                  webUrl: response.value[index].webUrl,
                  profileImageSrc: "",
                  user: {
                    displayName: response.value[0].createdBy.user.displayName,
                    email: response.value[0].createdBy.user.email
                  }
                });
              }
              if (myRecentDocuments.length > 0) {
                myRecentDocuments.forEach(recentDocument => {
                  client
                    .api(
                      "/users/" + recentDocument.user.email + "/photo/$value"
                    )
                    .version("v1.0")
                    .get((error, userProfileImg, body) => {
                      if (error) {
                        return error;
                      }
                      let u = userProfileImg;
                      let b = body;
                    });
                });
              }
              this.setState({
                myRecentDocuments: myRecentDocuments,
                loading: false,
                error: null
              });
            }
          })
          .catch((error: any) => {
            console.log(error);
          });
      }
    );
  }

Update componentDidMount method with below code

public componentDidMount(): void {
    this._loadDocuments();
  }

Replace render method with below code

 public render(): JSX.Element {
    const error: JSX.Element = this.state.error ? (
      <div>
        <strong>Error: </strong> {this.state.error}
      </div>
    ) : (
      <div />
    );
    const loading: JSX.Element = this.state.loading ? (
      <div style={{ margin: "0 auto" }}>
        <Spinner label={"Loading..."} />
      </div>
    ) : (
      <div />
    );
    const docs: JSX.Element[] = this.state.myRecentDocuments.map(
      (doc: IMyRecentDocument, i: number) => {
        return (
          <DocumentCard
            type={DocumentCardType.normal}
            onClickHref={doc.webUrl}
            key={doc.id}
          >
            <DocumentCardPreview
              previewImages={[
                {
                  name: doc.name,
                  linkProps: {
                    href: doc.webUrl,
                    target: "_blank"
                  },
                  previewImageSrc: TestImages.documentPreview,
                  iconSrc: TestImages.iconPpt,
                  imageFit: ImageFit.cover,
                  width: 318,
                  height: 196
                }
              ]}
            />
            <DocumentCardTitle title={doc.name} />
            <DocumentCardActivity
              activity={`${doc.user.displayName}`}
              people={[
                {
                  name: doc.user.displayName,
                  profileImageSrc: TestImages.personaFemale
                }
              ]}
            />
          </DocumentCard>
        );
      }
    );
    return (
      <div className={styles.myRecentDocument}>
        <div className={css("ms-font-xl", styles.label)}>
          {this.props.clientMode}
        </div>
        {loading}
        {error}
        {docs}
        <div style={{ clear: "both" }} />
      </div>
    );
  }

Step 7 : Open <Webpartname>Webpart.ts file under the <webpartname> folder.
Add below code in that file.

import { IGraphConsumerProps } from "./components/IGraphConsumerProps";
import * as strings from "MyRecentDocumentWebPartStrings";
import MyRecentDocument from "./components/MyRecentDocument";
import { IMyRecentDocumentProps } from "./components/IMyRecentDocumentProps";
import { ClientMode } from "./components/ClientMode";
export interface IMyRecentDocumentWebPartProps {
  description: string;
  clientMode: ClientMode;
}

Update Render method with below code

public render(): void {
    const element: React.ReactElement<
      IGraphConsumerProps
    > = React.createElement(MyRecentDocument, {
      clientMode: this.properties.clientMode,
      context: this.context
    });

    ReactDom.render(element, this.domElement);
  }

Step 8 : Run the below command in visual studio code terminal
gulp build
gulp bundle
gulp package-solution // generate a package file in SharePoint folder.
Upload package file in to the https://domainname.sharepoint.com/sites/apps

You will get the permission name as mention in above image.This permission reference generated from the package-solution.json file.Deploy it.
login in admin portal.
https://<tenentname>-admin.sharepoint.com/_layouts/15/online/AdminHome.aspx#/webApiPermissionManagement

Perform steps one by one

This process give the permission to access graph API from spfx web part.

Run below command in terminal
gulp serve –nobrowser
Open Below URL in browser

https://<tenentName>.sharepoint.com/sites/devSite/_layouts/15/workbench.aspx

Add Web part and you will get the list of documents in document cards.

Feedback is always welcome.
Happy Coding:)

One thought on “Graph + SPFx + Document Cards

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.