{"id":2563,"date":"2018-07-27T17:20:37","date_gmt":"2018-07-27T15:20:37","guid":{"rendered":"https:\/\/www.itidea.nl\/?p=2563"},"modified":"2018-11-13T14:45:21","modified_gmt":"2018-11-13T13:45:21","slug":"sentiment-of-comments-given-on-a-sharepoint-page","status":"publish","type":"post","link":"https:\/\/www.itidea.nl\/index.php\/sentiment-of-comments-given-on-a-sharepoint-page\/","title":{"rendered":"Sentiment of comments given on a SharePoint page"},"content":{"rendered":"<p>Microsoft Cognitive Services (formerly Project Oxford) are a set of APIs, SDKs and services available to developers to make their applications more intelligent, engaging and discoverable. Microsoft Cognitive Services expands on Microsoft\u2019s evolving portfolio of machine learning APIs and enables developers to easily add intelligent features \u2013 such as emotion and video detection; facial, speech and vision recognition; and speech and language understanding \u2013 into their applications.<\/p>\n<p>This blog gives you a quick peek at the part of detecting language and analyze sentiment in combination with the SharePoint Framework (SPFx), C# Azure Function and the Azure Cognitive Service: Text Analysis.<\/p>\n<p>For demo purposes I&#8217;m going to analyze the sentiment of the comments given on a page in a SharePoint site and store the results in a list.<\/p>\n<p><a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/09\/6-Sentiment-overview.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2667\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/09\/6-Sentiment-overview.png\" alt=\"\" width=\"872\" height=\"269\" srcset=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/09\/6-Sentiment-overview.png 872w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/09\/6-Sentiment-overview-300x93.png 300w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/09\/6-Sentiment-overview-768x237.png 768w\" sizes=\"auto, (max-width: 872px) 100vw, 872px\" \/><\/a><\/p>\n<h3>Comments<\/h3>\n<p>After publishing a Site Page or News Item the comments functionality becomes available.<\/p>\n<p>Comments are stored in a separate data store with references to list guids and item ids. This means when moving or copying a page the comments are lost.<\/p>\n<p>Using comments only a single level of replies is allowed.<\/p>\n<p><a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/1-Sentiment-Comments.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2572 size-medium\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/1-Sentiment-Comments-211x300.png\" alt=\"\" width=\"211\" height=\"300\" srcset=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/1-Sentiment-Comments-211x300.png 211w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/1-Sentiment-Comments.png 590w\" sizes=\"auto, (max-width: 211px) 100vw, 211px\" \/><\/a><br \/>\nFigure 1 &#8211; One level of replies is allowed<\/p>\n<p>To get the comments of a page in SharePoint a REST API can be used.<\/p>\n<p>Top level comments can be collected by using the list title or guid and the id of the page item, like _api\/web\/lists\/GetByTitle(&#8216;Site%20Pages&#8217;)\/GetItemById(1)\/Comments<\/p>\n<p>To also get the replies for each comment<br \/>\n_api\/web\/lists\/GetByTitle(&#8216;Site%20Pages&#8217;)\/GetItemById(1)\/Comments?$expand=replies<\/p>\n<p>For the purpose of this post a list is created where the library name and the page id can be stored. At this list listview commandset &#8216;Get comments&#8217; is available to get the comments when an item is selected.<\/p>\n<p><a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/2-Sentiment-Comments.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2571 size-full\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/2-Sentiment-Comments.png\" alt=\"\" width=\"585\" height=\"222\" srcset=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/2-Sentiment-Comments.png 585w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/2-Sentiment-Comments-300x114.png 300w\" sizes=\"auto, (max-width: 585px) 100vw, 585px\" \/><\/a><br \/>\nFigure 2 &#8211; List to store the info<\/p>\n<p>The code of the listview command set gets the library name and pageid from the selected item.<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nlet selectedPageItem: IPageItem = {\r\nlibrary: event.selectedRows&#x5B;0].getValueByName(&quot;Title&quot;),\r\npageId: event.selectedRows&#x5B;0].getValueByName(&quot;PageID&quot;)\r\n};\r\n<\/pre>\n<p>With this object the REST API is called<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nprivate async getComments(itemToGetComments: IPageItem): Promise&lt;string&gt; {\r\nconst currentWebUrl: string = this.context.pageContext.web.serverRelativeUrl;\r\nlet commentsUrl: string = `${currentWebUrl}\/_api\/web\/lists\/GetByTitle('${itemToGetComments.library}')\/GetItemById(${itemToGetComments.pageId})\/Comments`;\r\n\r\nconst response:HttpClientResponse = await this.context.spHttpClient.get(commentsUrl, SPHttpClient.configurations.v1);\r\n\r\nconst responseJSON = await response.json();\r\nconst comments: IComment&#x5B;] = responseJSON.value;\r\nlet commentsInfo:string&#x5B;] = comments.map((comment) =&gt; {\r\nconsole.log(comment.text);\r\nconsole.log(comment.replyCount);\r\nreturn(\r\ncomment.text\r\n)\r\n})\r\nreturn commentsInfo.join(&quot; | &quot;);\r\n\r\n}\r\n<\/pre>\n<p>And the item in the list is updated with the result<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nthis.getComments(selectedPageItem).then((comments: string) =&gt; {\r\n\/\/update list item with comments\r\nconsole.log(comments);\u00a0 \u00a0 \u00a0 sp.web.getList(this.context.pageContext.list.serverRelativeUrl).items.getById(event.selectedRows&#x5B;0].getValueByName(&quot;ID&quot;)).update({\r\n'Comments':comments\r\n});\r\n}).catch((exception) =&gt; {\r\nconsole.log(`Oeps... ${exception.message}`);\r\n});\r\n<\/pre>\n<p><a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/3-Sentiment-Comments.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2570 size-full\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/3-Sentiment-Comments.png\" alt=\"\" width=\"754\" height=\"241\" srcset=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/3-Sentiment-Comments.png 754w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/3-Sentiment-Comments-300x96.png 300w\" sizes=\"auto, (max-width: 754px) 100vw, 754px\" \/><\/a><br \/>\nFigure 3 &#8211; The comments are stored in the list<\/p>\n<p>Once the comments are available it&#8217;s time to get the sentiment out of them.<\/p>\n<h3>Sentiment<\/h3>\n<p>The comments will be used to determine positive or negative sentiment about the page the comment were given at.<\/p>\n<p>To use the Text Analysis API to make sentiment(al \ud83d\ude42 )\u00a0 calls a Cognitive\u00a0 Service has to be created in Azure.\u00a0 This has been explained in great detail in this article: <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/cognitive-services\/cognitive-services-apis-create-account\">https:\/\/docs.microsoft.com\/en-us\/azure\/cognitive-services\/cognitive-services-apis-create-account<\/a><\/p>\n<p>To be able to make a call the endpoint is needed and a key.<\/p>\n<p>The endpoint of the API is dependent of the location selected when the service was created, in my case West Europe.<\/p>\n<p>A key is needed to add to the request header &#8216;Ocp-Apim-Subscription-Key&#8217;, it is the subscription key which provides access to the API.<\/p>\n<p>Now it&#8217;s time to start Visual Studio and create an Azure function (or create an Azure function through the Azure portal).<\/p>\n<p>The next step is to add the Cognitive Services SDK to the project: NuGet package &#8216;Microsoft.Azure.CognitiveServices.Language&#8217;. The package is still in preview when writing this post, so select &#8216;include prerelease&#8217; using the NuGet Packages Manager.<\/p>\n<p>The HTTP endpoint could be called directly but the SDK make it much easier to call the service.<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nITextAnalyticsAPI client = new TextAnalyticsAPI(new ApiKeyServiceClientCredentials());\r\nclient.AzureRegion = AzureRegions.Westeurope;\r\n<\/pre>\n<p>In the code shown above a new instance of the Text Analytics API is initialized and the appropriate region is set. This has to be the same region when the Cognitive Service was created.<\/p>\n<p>The ApiKeyServiceClientCredentials class is a container for the subscription credentials and adds the\u00a0 &#8216;Ocp-Apim-Subscription-Key&#8217; request header with the key as value to the request.<\/p>\n<p>To determine the language of the comments the Language Recognition API is used.<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nLanguageBatchResult languageResult = await client.DetectLanguageAsync(\r\nnew BatchInput(new List&lt;Input&gt;()\r\n{\r\nnew Input(sentimentRequest.Id, sentimentRequest.Text)\r\n})\r\n);\r\nvar detectedLanguage = languageResult.Documents&#x5B;0].DetectedLanguages&#x5B;0];\r\n<\/pre>\n<p>The comments of a single page are stored together in the SharePoint list and offered as one text to analyze. Hereby I made the assumption that all comments on a single page will be in the same language, while separate pages can differ in language.<\/p>\n<p>The detected language is used as input in the Text Analytics API along with the Id and Text of the request. These are properties of the MultiLanguageBatchInput object.<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nSentimentBatchResult sentimentResult = await client.SentimentAsync(\r\nnew MultiLanguageBatchInput(\r\nnew List&lt;MultiLanguageInput&gt;()\r\n{\r\nnew MultiLanguageInput(detectedLanguage.Iso6391Name,\r\nsentimentRequest.Id, sentimentRequest.Text)\r\n}\r\n)\r\n);\r\n<\/pre>\n<p>The Azure function returns the score to the requester:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nreturn req.CreateResponse(HttpStatusCode.OK, sentimentResult.Documents&#x5B;0].Score);\r\n<\/pre>\n<h3>Update the list item<\/h3>\n<p>The function to get the sentiment is now in place.<\/p>\n<p>The next step is to call this function from another listview commandset to get the sentiment with the comments of a page as input.<\/p>\n<p>Get the input<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nlet selectedItem: ISentimentItem = {\r\nId: event.selectedRows&#x5B;0].getValueByName(&quot;ID&quot;),\r\nText: event.selectedRows&#x5B;0].getValueByName(&quot;Comments&quot;)\r\n};\r\n<\/pre>\n<p>The function to get the score<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nprivate async getSentiment(itemToAnalyze: ISentimentItem): Promise&lt;number&gt; {\r\nlet itemToAnalyzeJSON = JSON.stringify(itemToAnalyze);\r\n\r\nconst requestHeaders: Headers = new Headers();\r\nrequestHeaders.append(&quot;Content-type&quot;, &quot;application\/json&quot;);\r\nrequestHeaders.append(&quot;Accept&quot;, &quot;application\/json&quot;);\r\n\/\/requestHeaders.append(&quot;Ocp-Apim-Subscription-Key&quot;, &quot;we do not want this key here in script for everyone to read!&quot;);\r\nconst postOptions: IHttpClientOptions = {\r\nheaders: requestHeaders,\r\nbody: itemToAnalyzeJSON\r\n};\r\n\r\nconst afResponse: HttpClientResponse = await this.context.httpClient.post(this._functionUrl, HttpClient.configurations.v1, postOptions);\r\nconst score = await afResponse.json();\r\n\r\nreturn score;\r\n}\r\n<\/pre>\n<p>Call the function and update the list item<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nthis.getSentiment(selectedItem).then((score: number) =&gt; {\r\n\/\/update list item with sentiment score\r\nconsole.log(score);\r\nconsole.log(this.context.pageContext.list.serverRelativeUrl);\r\nsp.web.getList(this.context.pageContext.list.serverRelativeUrl).items.getById(event.selectedRows&#x5B;0].getValueByName(&quot;ID&quot;)).update({\r\n'RawSentiment':score\r\n});\r\n}).catch((exception) =&gt; {\r\nconsole.log(`Oeps... ${exception.message}`);\r\n});\r\n<\/pre>\n<h3>Putting it all together<\/h3>\n<p>To tie everything together in debug mode fire up the Azure function in Visual Studio by pressing F5.<\/p>\n<p><a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/4-Sentiment-Comments.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2569\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/4-Sentiment-Comments.png\" alt=\"\" width=\"410\" height=\"80\" srcset=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/4-Sentiment-Comments.png 410w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/4-Sentiment-Comments-300x59.png 300w\" sizes=\"auto, (max-width: 410px) 100vw, 410px\" \/><\/a><br \/>\nFigure 4 &#8211; The debugger of the Azure Function is listening<\/p>\n<p>Run &#8216;gulp serve&#8217; from the listview commandset project, wait for the browser to come up and select &#8216;Load debug scripts&#8217;.<br \/>\nIn the list one item is configured as shown in figure 3 with library name &#8216;Site Pages&#8217; and PageID 2.<br \/>\n&#8216;Get comments&#8217; was already pressed to get the comments of this page.<br \/>\nPress &#8216;Get Sentiment&#8217; to get the sentiment of the comments.<\/p>\n<p><a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/5-Sentiment-Comments.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2568\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/5-Sentiment-Comments.png\" alt=\"\" width=\"779\" height=\"234\" srcset=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/5-Sentiment-Comments.png 779w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/5-Sentiment-Comments-300x90.png 300w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/07\/5-Sentiment-Comments-768x231.png 768w\" sizes=\"auto, (max-width: 779px) 100vw, 779px\" \/><\/a><br \/>\nFigure 5 &#8211; The sentiment shown<\/p>\n<p>The raw score doesn&#8217;t look very attractive, so some column formatting will spice things up!<\/p>\n<h3>Summary<\/h3>\n<p>The new Cognitive services can be used to analyze all kinds of data in your organization. This post showed how to analyze the sentiment of comments on a page.<\/p>\n<p>This post is for demo purposes, so different steps are used here to explain the techniques, which could be combined in a production environment.<\/p>\n<p><a href=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/09\/6-Sentiment-overview.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2667\" src=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/09\/6-Sentiment-overview.png\" alt=\"\" width=\"872\" height=\"269\" srcset=\"https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/09\/6-Sentiment-overview.png 872w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/09\/6-Sentiment-overview-300x93.png 300w, https:\/\/www.itidea.nl\/wp-content\/uploads\/2018\/09\/6-Sentiment-overview-768x237.png 768w\" sizes=\"auto, (max-width: 872px) 100vw, 872px\" \/><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Microsoft Cognitive Services (formerly Project Oxford) are a set of APIs, SDKs and services available to developers to make their applications more intelligent, engaging and discoverable. Microsoft Cognitive Services expands on Microsoft\u2019s evolving portfolio of machine learning APIs and enables &#8230; <a class=\"more-link\" href=\"https:\/\/www.itidea.nl\/index.php\/sentiment-of-comments-given-on-a-sharepoint-page\/\">Read More &raquo;<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49,39,41,31],"tags":[50,37,46,48],"class_list":["post-2563","post","type-post","status-publish","format-standard","hentry","category-azure","category-office-365","category-sharepoint","category-visual-studio","tag-azure","tag-office365","tag-sharepoint","tag-spfx"],"_links":{"self":[{"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/posts\/2563","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/comments?post=2563"}],"version-history":[{"count":7,"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/posts\/2563\/revisions"}],"predecessor-version":[{"id":2575,"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/posts\/2563\/revisions\/2575"}],"wp:attachment":[{"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/media?parent=2563"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/categories?post=2563"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.itidea.nl\/index.php\/wp-json\/wp\/v2\/tags?post=2563"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}