Solution overview

Cresco is a system integrator that provides IT solutions and services to various industries. One of their customers is a life insurance company with more than 1,000 employees. This insurance company is considering automating internal meeting reservation to improve employee productivity. Cresco proposed a meeting reservation Bot for this subject. They developed a meeting reservation Bot that runs on Microsoft Teams with Bot Framework and Office 365 APIs.

Key technologies

  • Bot Framework for developing a meeting reservation Bot. https://dev.botframework.com/
  • Office 365 (Outlook) for scheduling meeting. https://developer.microsoft.com/en-us/outlook/
  • Office 365 (Microsoft Teams) for operating a meeting reservation Bot. https://dev.office.com/microsoft-teams
  • Microsoft Graph API for finding meeting time. https://developer.microsoft.com/ja-jp/graph
  • Microsoft Azure for deploying a meeting reservation Bot. https://azure.microsoft.com/ja-jp/services/app-service/web/

Project members

Cresco project members:

  • Takuhiro Inoue - AI Engineer, Technology Laboratory
  • Masaki Yoshida - IT Architect, Leading Technology Division
  • Yosuke Isa - System Engineer, Leading Technology Division
  • Ryota Ishikawa - System Engineer, System Engineering Center

Microsoft DX Japan project members:

  • Hiroyuki Watanabe - Technical Evangelist
  • Tsuyoshi Matsuzaki - Technical Evangelist

The picture of the Hackfest 1. Figure 1: The first day of the Hackfest

The picture of the Hackfest 2. Figure 2: The 4th day of the Hackfest

Customer profile

Cresco Ltd. https://www.cresco.co.jp/cresco_e/

Cresco has been involved in various business structured around the core technologies that comprise the skill of their craft. These technologies span over three technological fields of application development, IT infrastructure system architecture, and embedded systems. Over the course of four decades from the time of its founding in 1988 and even before that time, they have accumulated technologies unrivaled by their competitors. Their particular strength is in the technologies of IT infrastructure system architecture and embedded systems, which support the basic components of IT.

Problem statement

Business Issues: The end user of this project is a life insurance company with more than 1,000 employees. They hope to improve the productivity of their employees by automating reservations for internal meetings.

Technical Issues: Cresco proposed a meeting reservation Bot for solving the mentioned above. There were three technical problems they needed to resolve during the Hackfest.

  1. THey needed to add AD authentication function to the Bot.
  2. The Bot needed to access the Outlook calendar in order to make meeting reservations.
  3. The Bot needed to connect to Skype for Business or Microsoft Teams.

Solution and steps

Architecture diagrams The meeting reservation Bot architecture diagram. Figure 3: The meeting reservation Bot architecture diagram This is a diagram of the meeting reservation Bot architecture. Cresco developed a meeting reservation Bot with Bot Framework. The meeting reservation Bot accesses the Outlook calendar with Microsoft Graph APIs. The Bot works on Microsoft Azure as a Web App. The Bot connects to Microsoft Teams with the Bot Framework Connector.

Bot User operation flow diagrams The Bot user reserves the meeting at the appropriate time and place from the Bot by the following procedure.

  1. The user enters a schedule in the Outlook calendar. Figure 5: The meeting reservation Bot operation flow 1

  2. There are two reservation functions, “date appointment reservation” and “reservation now”. Figure 6: The meeting reservation Bot operation flow 2

  3. The user specifies the meeting date and time by button selection. Figure 7: The meeting reservation Bot operation flow 3

  4. The user specifies the number of participants in the meeting and the meeting place. The Bot responds with some appropriate meeting suggestions. Figure 8: The meeting reservation Bot operation flow 4

  5. When the user selects “Yes” to continue reservation, the meeting reservation is completed. Figure 9: The meeting reservation Bot operation flow 5

  6. The meeting schedule is also entered in the participant’s meeting calendar. Figure 10: The meeting reservation Bot operation flow 3

Technical delivery

This selection will include the following details of how the solution was implemented:

Prerequisites

The Bot Framework

Authentication Implementation The meeting reservation Bot authentication flow. Figure 4: The meeting reservation Bot authentication flow This is a diagram of the meeting reservation Bot authentication flow. Cresco developed the Bot authenticates with Azure AD Services. The Bot gets access token to call Microsoft Graph APIs. The Bot accesses the Outlook calendar with Microsoft Graph APIs. Cresco could implement the authentication function of this Bot using AuthBot library.

The following is a snipet from C# code of authentication function of this Bot. RootDialog.cs

   [Serializable]
    public class RootDialog : IDialog<object>
    {
        private const string ReserveDetail = "会議室予約(日時選択)";
        private const string ReserveInstant = "会議室予約(いますぐ)";
        private List<string> mainMenuList = new List<string>() { ReserveDetail, ReserveInstant };

        public async Task StartAsync(IDialogContext context)
        {
            await context.PostAsync("こんにちわ!会議室予約Botです。");
            context.Wait(MessageReceiveAsync);
        }

        private async Task MessageReceiveAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
        {
            var reply = await result;
            string accessToken = await context.GetAccessToken(ConfigurationManager.AppSettings["ActiveDirectory.ClientId"]);


            //Check login status
            if (string.IsNullOrEmpty(accessToken))
            {
                await context.PostAsync("OAuthログインしてください。");
                await context.Forward(new AzureAuthDialog(ConfigurationManager.AppSettings["ActiveDirectory.ClientId"]), this.ResumeAfterAuth, reply, CancellationToken.None);
            }
            else
            {
                await ShowFirstmenu(context);
            }
        }

        private async Task ResumeAfterAuth(IDialogContext context, IAwaitable<string> result)
        {
            var message = await result;
            await ShowFirstmenu(context);
        }

        private async Task ShowFirstmenu(IDialogContext context)
        {
            var accessToken = await context.GetAccessToken(ConfigurationManager.AppSettings["ActiveDirectory.ResourceId"]);

            var user = await GraphApiService.GetUser(accessToken);

            await context.PostAsync(string.Format("ようこそ,{0}! 私がお手伝い出来る事はありますか?", user.displayName));

            await Task.Run(() => 
                PromptDialog.Choice(context, this.CallDialog, this.mainMenuList, "Select Menu.")
            );
        }

        private async Task CallDialog(IDialogContext context, IAwaitable<string> result)
        {
            var selectedMenu = await result;
            switch (selectedMenu)
            {
                case ReserveDetail:
                    context.Call(new ReserveDetail(), ResumeAfterDialog);
                    break;
                case ReserveInstant:
                    context.Call(new ReserveInstant(), ResumeAfterDialog);
                    break;
            }
        }

        private async Task ResumeAfterDialog(IDialogContext context, IAwaitable<object> result)
        {

            // Repeat
            var RepeatMenuList = new List<string>() { "Yes", "No" };
            await Task.Run(() =>
                PromptDialog.Choice(context, this.RepeatDialog, RepeatMenuList, "続けて予約しますか ?")
            );
        }

        private async Task RepeatDialog(IDialogContext context, IAwaitable<string> result)
        {
            var selectedMenu = await result;

            switch (selectedMenu)
            {
                case "Yes":
                    await ShowFirstmenu(context);
                    break;
                case "No":
                    await context.PostAsync("かしこまりました、またお会いしましょう。");
                    break;
            }
        }

Graph APIs.

Graph API Implementation 1 The user specifies the number of participants in the meeting and the meeting place. This part is implemented with the user list API. Figure 8: The meeting reservation Bot operation flow 4

The following is a snipet from request and response of user list API. request

GET method
header : {
    "Content-Type" : "application/json",
    "Authorization" : "Bearer {your-token}"
}
url : https://graph.microsoft.com/v1.0/users

response

body : {
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users",
  "value": [
    {
      "id": "41d96b2a-812d-4637-8000-0f83ad0ff9f1",
      "businessPhones": [],
      // 会議室名  : 会議室情報として保持する必要あり(別APIのパラメータとして利用)
      "displayName": "27F_ComRoom01",
      "givenName": null,
      // jobTitle が 会議室〜 のものは会議室を示す
      // カンマ区切りの情報で 会議室,許容人数,場所 の構成となる
      "jobTitle": "会議室,10,品川本社",
      // 会議室のメールアドレス : 会議室情報として保持する必要あり(別APIのパラメータとして利用)
      "mail": "27f_comroom01@hackfest2017crescogmail.onmicrosoft.com",
      "mobilePhone": null,
      "officeLocation": null,
      "preferredLanguage": null,
      "surname": null,
      "userPrincipalName": "G6e2537a19fb94bb8b1716e082d3d7715@hackfest2017crescogmail.onmicrosoft.com"
    },
    {
      "id": "70cfb00d-6665-4420-96bb-15195f888bd4",
      "businessPhones": [],
      "displayName": "test",
      "givenName": null,
      // jobTitle が null の場合は通常ユーザ
      "jobTitle": null,
      "mail": "test@hackfest2017crescogmail.onmicrosoft.com",
      "mobilePhone": null,
      "officeLocation": null,
      "preferredLanguage": "ja-JP",
      "surname": null,
      "userPrincipalName": "test@hackfest2017crescogmail.onmicrosoft.com"
    },
    {...}
  ]
}

Graph API Implementation 2 The Bot responds with some appropriate meeting suggestions. This part is implemented with user_findmeetingtimes API.

The following is a snipet from request and response of user_findmeetingtimes API. request

POST method
header : {
    "Content-Type" : "application/json",
    "Authorization" : "Bearer {your-token}",
    "Prefer" : "outlook.timezone=\"UTC\""
}
url : https://graph.microsoft.com/beta/me/findMeetingTimes

"body" : {
  // 会議参加者のリスト(今回は使わないので attendees のkey自体削除すること)
  "attendees": [
    {
      "type": "required",
      "emailAddress": { 
        "name": "guest user01",
        "address": "guest_user01@hackfest2017crescogmail.onmicrosoft.com" 
      }
    },
    {
      "type": "required",
      "emailAddress": { 
        "name": "guest user02",
        "address": "guest_user02@hackfest2017crescogmail.onmicrosoft.com" 
      }
    }
  ],
  // 利用する会議室情報
  "locationConstraint": {
    // 問い合わせた会議室情報だけで検索 
    "isRequired": "true",  
    // 空きがあるところを提案
    "suggestLocation": "false",  
    // 対象とする会議室の一覧
    "locations": [ 
      { 
        "resolveAvailability": "false",
        "locationEmailAddress": "27f_comroom01@hackfest2017crescogmail.onmicrosoft.com"
      } ,
      {
        "resolveAvailability": "false",
        "locationEmailAddress": "yb_room02@hackfest2017crescogmail.onmicrosoft.com"
      }
    ] 
  },
  // 大まかな時間帯の指定
  "timeConstraint": { 
    "timeslots": [
      { 
        // start end はUTCで記載でき、複数登録可能
        // 日本時間だと3/22 18:00〜20:00
        "start" : {
          "dateTime": "2017-03-22T09:00:00Z",
          "timeZone": "UTC"
        },
        "end" : {
          "dateTime": "2017-03-22T11:00:00Z",
          "timeZone": "UTC"
        }
      },
      // 日本時間だと3/22 13:00〜15:00
      {
        "start" : {
          "dateTime": "2017-03-22T04:00:00Z",
          "timeZone": "UTC"
        },
        "end" : {
          "dateTime": "2017-03-22T06:00:00Z",
          "timeZone": "UTC"
        }
      } 
    ] 
  },  
  // 会議時間 PT1H だと1時間を指定
  // この設定だと timeConstraint で設定した大まかな時間帯の中で1時間空いているところが候補になる
  "meetingDuration": "PT1H",
  "returnSuggestionReasons": "true",
  // 参加可能な人数の割合の閾値 80 だと80%を意味している
  "minimumAttendeePercentage": "80"
}

response

// 応答のサンプル
"body" : {
  "@odata.context": "https://graph.microsoft.com/beta/$metadata#microsoft.graph.meetingTimeSuggestionsResult",
  "emptySuggestionsReason": "",
  "meetingTimeSuggestions": [
    {
      "confidence": 100,
      "organizerAvailability": "free",
      "suggestionReason": "Suggested because it is one of the nearest times when all attendees are available.",
      "meetingTimeSlot": {
        "start": {
          "dateTime": "2017-03-22T05:00:00.0000000",
          "timeZone": "UTC"
        },
        "end": {
          "dateTime": "2017-03-22T06:00:00.0000000",
          "timeZone": "UTC"
        }
      },
      "attendeeAvailability": [
        {
          "availability": "free",
          "attendee": {
            "type": "required",
            "emailAddress": {
              "address": "guest_user01@hackfest2017crescogmail.onmicrosoft.com"
            }
          }
        },
        {
          "availability": "free",
          "attendee": {
            "type": "required",
            "emailAddress": {
              "address": "guest_user02@hackfest2017crescogmail.onmicrosoft.com"
            }
          }
        }
      ],
      "locations": [
        {
          "displayName": "27F_ComRoom01",
          "locationEmailAddress": "27_comroom01@hackfest2017crescogmail.onmicrosoft.com"
        }
      ]
    }
  ]
}

Graph API Implementation 3 The meeting schedule is entered in the participant’s meeting calendar. This part is implemented with calendar/events API. Figure 10: The meeting reservation Bot operation flow 3

The following is a snipet from request and response of calendar/events API. request

POST method
header : {
    "Content-Type" : "application/json",
    "Authorization" : "Bearer {your-token}"
}
url : https://graph.microsoft.com/v1.0/me/calendar/events

"body" : {
  // 会議室の場所
  "location" : {
    // 必ずresourceを設定すること
    "type": "resource",
    "displayName":"27f_ComRoom01",
    "locationEmailAddress": "27f_comroom01@hackfest2017crescogmail.onmicrosoft.com"
  },
  // 追加参加者
  "attendees" : [
    {
      // 必ず会議室ユーザを追加すること
        "emailAddress": {
          "name":"27f_ComRoom01",
          "address": "27f_comroom01@hackfest2017crescogmail.onmicrosoft.com"
        }
      }
  ],
  // 開始と終了をUTCで記載
  "start" : {
    "dateTime": "2017-03-21T00:00:00Z",
    "timeZone": "UTC"
  },
  "end" : {
    "dateTime": "2017-03-21T01:00:00Z",
    "timeZone": "UTC"
  },
  // bot 経由で登録したことがわかるメッセージを設定(固定でOK)
  "body" : {
    "content": "This event was reserved by bot.",
    "contentType": "Text"
  }
}

response

// 応答のサンプル
"body" : {
  "@odata.context": "https://graph.microsoft.com/beta/$metadata#users('70cfb00d-6665-4420-96bb-15195f888bd4')/calendar/events/$entity",
  "@odata.etag": "W/\"WQSNfH/PXEKLEz2Hn95oxwAAAAAfVA==\"",
  "id": "AQMkADZmY2I4MjM5LWZmADA1LTQyNzAtYjg5OC1iMjRjYjdkN2UzZDAARgAAAwEbiXeb7qRNtsFsAeseqGgHAFkEjXx-z1xCixM9h5-eaMcAAAIBDQAAAFkEjXx-z1xCixM9h5-eaMcAAAIcxwAAAA==",
  "createdDateTime": "2017-03-15T05:51:38.3084715Z",
  "lastModifiedDateTime": "2017-03-15T05:51:38.4295729Z",
  "changeKey": "WQSNfH/PXEKLEz2Hn95oxwAAAAAfVA==",
  "categories": [],
  "originalStartTimeZone": "UTC",
  "originalEndTimeZone": "UTC",
  "iCalUId": "040000008200E00074C5B7101A82E0080000000041CD6B36509DD201000000000000000010000000A8BC0FD80FFECF40983233F24654D5AF",
  "reminderMinutesBeforeStart": 15,
  "isReminderOn": true,
  "hasAttachments": false,
  "subject": null,
  "bodyPreview": "",
  "importance": "normal",
  "sensitivity": "normal",
  "isAllDay": false,
  "isCancelled": false,
  "isOrganizer": true,
  "responseRequested": true,
  "seriesMasterId": null,
  "showAs": "busy",
  "type": "singleInstance",
  "webLink": "https://outlook.office365.com/owa/?itemid=AQMkADZmY2I4MjM5LWZmADA1LTQyNzAtYjg5OC1iMjRjYjdkN2UzZDAARgAAAwEbiXeb7qRNtsFsAeseqGgHAFkEjXx%2Fz1xCixM9h5%2FeaMcAAAIBDQAAAFkEjXx%2Fz1xCixM9h5%2FeaMcAAAIcxwAAAA%3D%3D&exvsurl=1&path=/calendar/item",
  "onlineMeetingUrl": null,
  "responseStatus": {
    "response": "organizer",
    "time": "0001-01-01T00:00:00Z"
  },
  "body": {
    "contentType": "text",
    "content": ""
  },
  "start": {
    "dateTime": "2017-03-21T10:00:00.0000000",
    "timeZone": "UTC"
  },
  "end": {
    "dateTime": "2017-03-21T11:00:00.0000000",
    "timeZone": "UTC"
  },
  "location": {
    "displayName": "27F_ComRoom01"
  },
  "recurrence": null,
  "attendees": [
    {
      "type": "resource",
      "status": {
        "response": "none",
        "time": "0001-01-01T00:00:00Z"
      },
      "emailAddress": {
        "name": "27F_ComRoom01",
        "address": "27f_comroom01@hackfest2017crescogmail.onmicrosoft.com"
      }
    }
  ],
  "organizer": {
    "emailAddress": {
      "name": "test",
      "address": "test@hackfest2017crescogmail.onmicrosoft.com"
    }
  }
}

Customer Requests

Graph APIs

  1. Cresco would like to add an API to get the meeting room list and an API to get an equipment list. They embedded the code indicating the meeting room in JobTitles and filtered it.

  2. Cresco would like to know the best practice of how to use the findmeetingtimes API.

  3. Cresco would like to treat non-logged in users to Bot as conference organizers.

Conclusion

Cresco finished implementing the meeting reservation Bot using the Bot Framework and some Graph APIs. They discovered that they could implement a Bot in a short period time with the Bot Framework. They also discovered the meeting reservation function can be easily implemented by using the Graph APIs, especially the findMeeingTimes API.

This meeting reservation Bot was introduced to the end user (the insurance company). It was confirmed that meeting reservation time is shortened and contributes to improving their internal productivity.

Cresco were able to widen their knowledge of the Bot Framework and Office 365 Graph API through this project. They have built their Bot solution using Microsoft technology and they have gained a more efficient way to prepare proposals for their customer in the various industry.