Advanced | Flow of The Week - Integrate Facebook Workplace with your SharePoint Intranet

Facebook @workplace integration with SharePoint using Microsoft Flow

Hello Flow Fans! 

This weeks Flow of The Week comes from Tomasz Poszytek - Originaly posted on his Blog and re-posted with permission

This post will show how to connect your SharePoint intranet with a @workplace by Facebook community area dedicated for enterprises. The aim of the flow is to automatically gather and store every new post, comment and other sort of activities that can occur on a @workplace side in a dedicated list within SharePoint site.

Because there is yet no ootb solution to show @workplace activity feed, this approach will let you to have your data inside SharePoint and then to show it to your users using a built-in list web part or by creating a custom display web part. Choice is yours! Let’s begin.

 

Setting up @workplace and Flow

 

Step no 1 – create custom integration in @workplace

The integration is done via a webhook configured on a @workplace side. To be able to use the webhook, first it needs to be defined. To do that you need to go to "Integrations" page (being a @workplace administrator): https://[your-company].facebook.com/work/admin/?section=apps&ref=bookmarks and then to create new custom integration:

Then you need to define its name and optionally a description:

Now you need to select what permissions does the integration need (in my case this is only "Read group content"), then select groups, to which it will have access (in my cases this is "All groups")

and finally configure the webhooks.

Remember, that you can only subscribe one URL per webhook topic, but you may use the same URL for multiple topics.

In my case I only configured webhooks for the "Groups". How? Read below.

 

Step 2 – webhook configuration and verification

 

To configure and save a webhook it must have verified connection with the callback URL. The callback URL is in fact a URL of the Microsoft Flow, which is going to receive and parse data.

The tricky thing is, that the callback verification must be done using GET request, whereas further webhooks' requests are done using POST calls.

To do that simply create a Flow with a "Request" action as the trigger.

Then parse the header parameters, using the "Parse JSON" action:

  1. Content: triggerOutputs()['queries']
  2. Schema:
    { "type": "object", "properties": { "hub.mode": { "type": "string" }, "hub.challenge": { "type": "string" }, "hub.verify_token": { "type": "string" } } }

Finally add the "Response" action, and use "hub.challenge" value as a Body:

Do not forget, to change how the Flow can be triggered - set the method to "GET" (1) in trigger action (it's under the "advanced options"), publish your workflow and finally copy its URL (2):

Now paste that copied URL in a "Callback URL" field in webhook configuration and type any "Verify Token" value (it should be used to check, if the GET request really comes from your webhook) and hit "Save":

Now you're ready to go - you will notice in your Flow a new run, completed successfully and your new "Custom integration" will be saved. 

Whenever you decide to change ANYTHING in your Custom Integration configuration, you will need to verify Callback URL again - so again to send a GET request to your Flow.

Data structure

For saving comments I am using a single SharePoint list built from the following columns:

  1. Title - holds type of the information.
  2. Author - a text column to keep name and last name of an author.
  3. Date - when the event occurred (from webhook).
  4. Post/ Comment - a multiline text field, allowing HTML formatting, to keep body of the message.
  5. Image - again, a multiline text field, allowing HTML formatting, to store <img> tag with the URL of the image, attached to a message - this is because Flow does not support "Picture/ Hyperlink" fields having set type to "Picture".
  6. SourceURL - a "Picture/ Hyperlink" field with a type set to "Hyperlink".
  7. AuthorPPL - again, an author field, but this time as a "Person or group" field. My Flow is trying to map Author data to an existing SP User.
  8. ItemId - an identifier of the message: comment_id, post_id or a member_id.

The list, filled up with data, looks as below:

Building a Flow

Remember to keep the actions for GET response within your workflow. I did it by adding a condition action, with a condition: "1 is equal to 2", so that Flow always goes the other way. I am changing its behavior manually, between receiving GET and POST requests, which is impossible to automate right now:

Whenever I modify my @workplace Custom integration settings, I change the method to "GET" and the condition to "1 is equal 1". After verification passes, I am turning them back to "POST" and "1 is equal to 2". Some kind of a lifehack 😉

Step 1 - Request body schema

In my "POST" branch, the first action is "Parse JSON", used to parse the request body. Before I made a valid schema, I did dozens of calls from @workplace to Flow, to see how specific actions are represented in JSON. I have named the following scenarios:

  1. Membership - when a new user joins a group
  2. Comment - when a new comment is created
  3. Post - when a new post is written
    1. Post with a single photo
    2. Post with multiple photos
    3. Post without photo (a status)
    4. Event
    5. all others

In the end the schema looks like below (it contains properties for all scenarios, set to not be mandatory):

{

    "type": "object",

    "properties": {

        "entry": {

            "type": "array",

            "items": {

                "type": "object",

                "properties": {

                    "changes": {

                        "type": "array",

                        "items": {

                            "type": "object",

                            "properties": {

                                "field": {

                                    "type": "string"

                                },

                                "value": {

                                    "type": "object",

                                    "properties": {

                                        "from": {

                                            "type": "object",

                                            "properties": {

                                                "id": {

                                                    "type": "string"

                                                },

                                                "name": {

                                                    "type": "string"

                                                }

                                            }

                                        },

                                        "member": {

                                            "type": "object",

                                            "properties": {

                                                "id": {

                                                    "type": "string"

                                                },

                                                "name": {

                                                    "type": "string"

                                                }

                                            }

                                        },

                                        "update_time": {

                                            "type": "string"

                                        },

                                        "verb": {

                                            "type": "string"

                                        },

                                        "community": {

                                            "type": "object",

                                            "properties": {

                                                "id": {

                                                    "type": "string"

                                                }

                                            }

                                        },

                                        "actor": {

                                            "type": "object",

                                            "properties": {

                                                "id": {

                                                    "type": "string"

                                                },

                                                "name": {

                                                    "type": "string"

                                                }

                                            }

                                        },

                                        "attachments": {

                                            "type": "object",

                                            "properties": {

                                                "data": {

                                                    "type": "array",

                                                    "items": {

                                                        "type": "object",

                                                        "properties": {

                                                            "url": {

                                                                "type": "string"

                                                            },

                                                            "subattachments": {

                                                                "type": "object",

                                                                "properties": {

                                                                    "data": {

                                                                        "type": "array",

                                                                        "items": {

                                                                            "type": "object",

                                                                            "properties": {

                                                                                "url": {

                                                                                    "type": "string"

                                                                                },

                                                                                "media": {

                                                                                    "type": "object",

                                                                                    "properties": {

                                                                                        "image": {

                                                                                            "type": "object",

                                                                                            "properties": {

                                                                                                "src": {

                                                                                                    "type": "string"

                                                                                                },

                                                                                                "width": {

                                                                                                    "type": "number"

                                                                                                },

                                                                                                "height": {

                                                                                                    "type": "number"

                                                                                                }

                                                                                            }

                                                                                        }

                                                                                    }

                                                                                },

                                                                                "type": {

                                                                                    "type": "string"

                                                                                },

                                                                                "target": {

                                                                                    "type": "object",

                                                                                    "properties": {

                                                                                        "url": {

                                                                                            "type": "string"

                                                                                        },

                                                                                        "id": {

                                                                                            "type": "string"

                                                                                        }

                                                                                    }

                                                                                },

                                                                                "title": {

                                                                                    "type": "string"

                                                                                }

                                                                            }

                                                                        }

                                                                    }

                                                                }

                                                            },

                                                            "media": {

                                                                "type": "object",

                                                                "properties": {

                                                                    "image": {

                                                                        "type": "object",

                                                                        "properties": {

                                                                            "src": {

                                                                                "type": "string"

                                                                            },

                                                                            "width": {

                                                                                "type": "number"

                                                                            },

                                                                            "height": {

                                                                                "type": "number"

                                                                            }

                                                                        }

                                                                    }

                                                                }

                                                            },

                                                            "type": {

                                                                "type": "string"

                                                            },

                                                            "description": {

                                                                "type": "string"

                                                            },

                                                            "target": {

                                                                "type": "object",

                                                                "properties": {

                                                                    "url": {

                                                                        "type": "string"

                                                                    },

                                                                    "id": {

                                                                        "type": "string"

                                                                    }

                                                                }

                                                            },

                                                            "title": {

                                                                "type": "string"

                                                            }

                                                        }

                                                    }

                                                }

                                            }

                                        },

                                        "type": {

                                            "type": "string"

                                        },

                                        "target_type": {

                                            "type": "string"

                                        },

                                        "comment_id": {

                                            "type": "string"

                                        },

                                        "post_id": {

                                            "type": "string"

                                        },

                                        "created_time": {

                                            "type": "string"

                                        },

                                        "message": {

                                            "type": "string"

                                        },

                                        "permalink_url": {

                                            "type": "string"

                                        }

                                    }

                                }

                            }

                        }

                    },

                    "id": {

                        "type": "string"

                    },

                    "time": {

                        "type": "number"

                    }

                },

                "required": [

                    "changes",

                    "id",

                    "time"

                ]

            }

        },

        "object": {

            "type": "string"

        }

    }

}

 

Step 2 – setting up actions

After parsing request body using the schema, Flow must do the following steps:

  1. Get operation field – whether post is a “Comment”, “Membership” or “Posts”
    I do it using the “Compose” action and the following expressions:
    body('Parse_request_body')?['entry']?[0]?['changes']?[0]?['field']
     
  2. Switch branches based on the operation type:

  1. For each type I am getting a proper ID (ex. comment_id for “Comments” or post_id for “Posts”)
  2. Then it must query SharePoint to check, whether there is already a record with that ID. If there is, then obviously workflow ends its execution:

  1. If this is a “Post” type, then I am looking an internal operation type property using the following expression:
    body('Parse_request_body')?['entry']?[0]?['changes']?[0]?['field']

  1. Based on the outcome, Flow switches between:
    1. Photo
    2. Status
    3. Event
    4. and other requests (haven’t seen anything “other” yet)
       
  2. Then for “Photo” type it also checks, whether there is only one or multiple photos (gallery) checking, or if the type is “album” by evaluating the below expression:
    body('Parse_request_body')?['entry']?[0]?['changes']?[0]?['value']['attachments']['data']?[0]?['type']
     
  3. Finally, for each message type it is also trying to match user with an existing one, by using action “Office 365 Get user profile (V2)” and concatenating users first and last name together with company’s domain as a UPN:
    concat(trim(replace(trim(body('Parse_request_body')?['entry']?[0]?['changes']?[0]?['value']['from']['name']), ' ', '.')), '@the-company.com')
     
  4. Obviously, if user is not found, the action triggers error, so for that case the next action, which is “Create list item” has configured “run after” settings also to “has failed”, so that no matter what happens, Flow won’t fail for that reason:

  1. In the end Flow is creating new item on a list, combining all information together:

Structure of Flow's actions, on a medium level of details, looks as below for the described solution:

Icons (i) visible next to arrows mark relations, in which the next action is executed even if the previous one ends up with failure.

Structure of a single block, used to save data from @workplace into a SharePoint list, looks as on the example below (it shows saving data for a new post):

Things to remember

During that project I learnt the following things. Please, keep them in mind when trying to follow my steps:

  1. @workplace will send GET request to FLOW every time a "Custom integration" is modified and saved.
  2. @workplace will be sending requests for a single event as long as it won't receive a "200 OK" response - be prepared to verify if the content has already been added.
  3. @workplace is not always waiting for the response. I see dozens of failed flows just because
  4. @workplace abandoned waiting for the response. I have no idea why, it seems to be really random. Anyway - in case it fails waiting, it will try to send the same data again and again until receiving "200 OK". Only the frequency will be lower and after couple of tries it will stop.
  5. Using the "run after" configuration in Flow's actions can be really useful - both for overcoming missing information or actions which can end up with errors which we don't really care about, as well as doing conditional blocks. In my Flow I was using this configuration very often.

Thanks for reaching this sentence. I hope you find it useful. Don't hesitate to contact me in case you have any questions or leave your comment below!