How to gather data from Miro

Miro is an online collaborative whiteboard platform which we are using heavily in our consulting workshops to collaborate, explore and document. In this blog post I will show you options to automatically gather, modify and analyse Miro boards.

Motivation

In one of our Innovation Incubator rounds, the idea was born to find ways to analyse Big Picture Event storms and their evolution over time in the course of the workshop’s post processing. Wouldn’t it be helpful if you could create a replay cockpit where you can replay the whole workshop and see how this event storm evolved over time.

Additionally we needed to export board data into our statistical analyzers written in python. Basic data such as stickers and their history needed to generate word clouds, lingual analysis and activity analysis of workshop participants.

So the idea was born to check out the Miro Options to gather data and create ideas how this data can be analysed.

Options to access Miro Data

MIRO provides two ways of accessing data. You can either use the Miro REST API or the Miro Web Plugin SDK. The REST API provides easy access to basic board data but it is not yet comprehensive whereas the Miro SDK provides more capabilities.

Retrieve Data

The REST API is the simplest way to retrieve data from Miro. The following preparatory steps need to be executed to access the API (detailed description can be found here)

1. Create a developer team

2. Create an APP within your organization. Go to your User->Profile Settings->Your Apps

3. Create a developer team

4. Create an APP within your organization: Go to your User->Profile Settings —> Your Apps

Once you have gathered you personal access (bearer) token, the first request can be sent to Miro on behalf of your Miro user. You can access any resource your Miro user has access to, limited by the app permission scope (intersection of both permissions). It is also possible to send requests on behalf other users, however this requires that you set up an OAUTH 2.0 flow. In this Python sample request we retrieve all widgets from a board.

 url = "https://api.miro.com/v1/boards/" + board_id + "/widgets"

    headers = {
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Authorization": bearer
    }

    response = requests.request("GET", url, headers=headers)

This sample response contains JSON describing all the widgets on a board. As you can see there is a lot of styling and positioning information delivered. Everything within a frame is embedded as sub collection within this frame.

{
  "type": "collection",
  "data": [
    {
      "type": "sticker",
      "scale": 0.54,
      "y": -131.5,
      "x": 250.0,
      "text": "<p>t</p>",
      "style": {
        "backgroundColor": "#fff9b1",
        "fontAutoSize": true,
        "fontFamily": "OpenSans",
        "fontSize": 64.0,
        "lineHeight": 1.36,
        "textAlign": "center",
        "textAlignVertical": "middle"
      },
      "id": 3074457345618258749,
      "createdBy": {
        "type": "user",
        "name": "igor",
        "id": "3074457345618258730"
      },
      "modifiedAt": "2019-04-06T14:19:35Z",
      "modifiedBy": {
        "type": "user",
        "name": "igor",
        "id": "3074457345618258730"
      },
      "createdAt": "2019-04-06T14:17:04Z"
    }
  ]
  }

Limits and Observations:

  • You can retrieve max 1000 widgets. Pagination is not supported. If you have more elements on your board, you could filter by widget type and send a separate request for each type. If you use a huge board to collect the outcome of several workshops this limit could be a hurdle.

  • You can retrieve the created by and modified by metadata however the modified by describes the last modification. There is no way to retrieve the full history of a widget.

  • Updating the App Scopes had no effect. I needed to delete and then recreate the App defining new scopes.

Modify Data

The REST API provides methods to create and modify boards. For our replay cockpit we need first to retrieve all elements and then replay them in the correct order. My assumption that everything I retrieve can be posted again to another board was refuted. The following limitations have been observed

  • Deleting boards is not possible. This lacks the ability during the development phase to create boards and automatically delete them after each iteration. My workaround was to delete all widgets on the cleanup step, with the side effect that the board crashed after several iterations (probably too many elements in history)

  • Styles: Not each style property can be set in a POST request even if was delivered by the GET request.

  • Once you retrieve colors they are in a 8 digits format (RGB including opacity). Write requests accept only 6 digit formats (RGB)

  • 12 colors are support for stickers however on the board you could assign custom colors to stickers. I wrote therefore a color distance calculator to map a custom color to the nearest one of these 12

Actually I do not understand some of these limitations and it took several runs and hacks to copy the content from one board to another board.

Replay Architecture

I have set up a vue.js application with a python backend. The backend is due to convenience reasons and to prevent CORS issues. The user then can select a board. All board content is loaded chronological into a slider and the user can navigate through the single steps. Once the autoplay feature is enabled, you can go to the replay Miro board and enjoy the replay show. For each replay step either a create widget REST request is sent or a delete widget request (if you navigate backwards)

SDK

As we could solve our replay cockpit using the API we searched for a small use case to evaluate the Miro SDK. Wouldn’t it be nice to implement a word counter which shows you within a widget the most used words and once you click on the word all occurrences on the board will be highlighted.

Your Code

Miro plugins are loaded from a public accessible source (your code) and embedded into the miro site via an IFrame. Therefore you need to setup an HTML site and publish it to some publicly available location, in my case github pages. In the simplest form you provide a html page which loads some javascript code.

let icon =
    '<circle cx="12" cy="12" r="9" fill="none" fill-rule="evenodd" stroke="currentColor" stroke-width="2"></circle>'
miro.onReady(() => {
    miro.initialize({
        extensionPoints: {
            bottomBar: {
                title: 'Word count',
                svgIcon: icon,
                positionPriority: 1,
                onClick: () => {
                    miro.board.ui.openLeftSidebar('/my-first-miro-plugin/wordCount.html')
                },
            },
        },
    })
})

This simple javascript file defines an action tray icon and assigns the content of wordCount.html to the sidebar on the left.

icon is displayed in the actions bar

Once you click on the icon the content of wordCount.html will be loaded to the left sidebar.

var tokens = new Object();

function addToWordCounts(element) {
    var text = normalizeTitle(element['text'])
    if (text == '')
        return;

    let parts = text.split(' ');
    for (const part of parts) {
        if (part in tokens) {
            tokens[part].elements.push(element)
            tokens[part].count++;
        } else {
            let newElement = new Object();
            newElement.elements = [element];
            newElement.count = 1;
            newElement.text = part;
            tokens[part] = newElement;
        }
    }
}

async function calculateWordCounts() {
    let widgets = await miro.board.widgets.stickers.get()
    tokens = new Object();
    for (const widget of widgets) {
        addToWordCounts(widget)
    }
}

miro.onReady(() => {
    // subscribe on user selected widgets
    //miro.addListener(miro.enums.event.SELECTION_UPDATED, getWidget)
    calculateWordCounts()
})

Miro provides the miro instance where you can hook to miro events and call miro functions to access and modify board content.

Finally you need to setup the application in the miro settings. Same as for REST API access you need to create an app, define the scope and permissions and additionally specify the public available source code URL.

SDK - Limits and first impressions

There was not the coding experience I am used to, especially if you are not familiar with the SDK functions and you need to try out things. Anytime you update your code you need to reload the miro board, click on your icon and then in worst case click through some workflows to reach the point which you want to test. As there is no local Miro dev environment provided the whole dev cycle gets bumpy. I worked a lot with the dev tools to find out possible functions on the current miro state and also to call them to test the outcome before I deploy the new code fragment to my public location.

Unfortunately the full history of a sticker cannot be gathered. As a workaround we had the idea to record changes and write them to local storage or an external destination. However, Miro’s change events are triggered once you create a sticker but not once a text is changed.

Summary

Miro REST API is a straightforward way to retrieve and modify basic Miro data. Unfortunately it is still under development and important data such as history cannot be gathered yet. If you need more control over data or even need a widget on your board, Miro SDK should be you choice. Coding within the SDK feels bumpy and there is still no way to retrieve the full history of stickers. I’m looking forward getting these features in future releases of Miro.

Zurück
Zurück

Creating solutions and projects in VS code

Weiter
Weiter

Creating solutions and projects in VS code