Skip to main content

We use a bunch of boards as templates and my colluagues have gone wild with links within a board.

When I duplicate a board or when I import it as a backup, all of those links still point to the original board.

I tried editing the backup file and making the link `?movetoWidget=283917482397498324`, whicn in theory should work, but the app prepends `http://` and claims the link is invalid.

I tried the API, but many oobjects that can contain links can't be updated. Even ones that are documented, like `shape`.

My code:
 

$goodboardId = "o9J_xxxxx"
$badBoardId = "o9J_yyyyy"

$goodBoard = "https://miro.com/app/board/o9J_xxxxx#61;"
$badboard = "https://miro.com/app/board/o9J_yyyyyyy="

$boardUri = "https://api.miro.com/v1/boards/$goodboardId"

$headers = @{ Authorization  = 'Bearer XXXXXXX'}

$supportedTypes = @("Sticker", "Shape", "Text", "Line", "Card")

$widgets = Invoke-RestMethod -Uri "$boardUri/widgets/" -Method GET -Headers $headers
$widgets.data | ?{ $_.text } | %{
    $originalText = $_.text 
    $newText = $originalText -replace $badboard,$goodBoard

    if ($originalText -ne $newText)
    {      
        if ($supportedTypes -contains $_.type)
        {
            $widget = $_ # Invoke-RestMethod -Uri "$boardUri/widgets/$($_.id)" -Method GET -Headers $headers
            $widget.text = $newText
            $patch = @{ 
                id = $widget.id
                text = $newText 
                type = $widget.type
            }
            $body = $patch | ConvertTo-Json -Depth 100

            try {
                Invoke-RestMethod -Uri "$boardUri/widgets/$($_.id)" -Method PATCH -Headers $headers -Body $body
            }
            catch {
                Write-Error ($patch | ConvertTo-Json -Depth 100)
                throw
            }
           
        }
        else 
        {
            Write-Error "Could not update widget:"
            Write-Debug $_
        }
    }
}

Results in:
 

{
  "text": "<p><a href=\"https://miro.com/app/board/o9J_xxxxxc&61;/?moveToWidget&#61;3074457352041839530&amp;cot&#61;10\" style=\"color:rgb(255,255,255)\">INTRODUCE</a></p><p><a href=\"https://miro.com/app/board/o9J_xxxxxx#61;/?moveToWidget&#61;3074457352041839530&amp;cot&#61;10\" style=\"color:rgb(255,255,255)\">YOURSELF</a></p>",
  "id": "3074457352436545188",
  "type": "shape"
}
Invoke-RestMethod: C:\Users\jesse\Desktop\Fix-Board-Links.ps1:33:17
Line |
  33 |  …             Invoke-RestMethod -Uri "$boardUri/widgets/$($_.id)" -Meth …
     |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | {   "status" : 415,   "code" : "mediaTypeNotSupported",   "message" : "Specified media type not supported for this endpoint",   "context" : null,   "type" : "error" }

Plus, it seems the API can't patch "locked" objects, nor do I have a unlock/lock option in the patch command as fas as I can tell.

not breaking in-board-links seems a pretty normal thing to ask...

This code seems to be able to patch the text across all shapes on the board:

 

$goodboardId = "o9J_aaaaaaa"
$badBoardId = "o9J_bbbbbb"

$boardUri = "https://api.miro.com/v1/boards/$goodboardId%3D"

$headers = @{ 
    Authorization  = 'Bearer Jxxxxxxxxxxxxxxxxxxxx'
    'Content-Type' = 'application/json'
}

$supportedTypes = @("Sticker", "Shape", "Text", "Line", "Card")

$widgets = Invoke-RestMethod -Uri "$boardUri/widgets/" -Method GET -Headers $headers
$widgets
$widgets.data | ?{ $_.text } | %{
    $originalText = $_.text 
    $newText = $originalText -replace $badBoardId,$goodboardId

    if ($originalText -ne $newText)
    {      
        if ($supportedTypes -contains $_.type)
        {
            $patch = @{ 
                text = $newText
            }
            $body = $patch | ConvertTo-Json -Depth 100

            try {
                Invoke-RestMethod -Uri "$boardUri/widgets/$($_.id)" -Method PATCH -Headers $headers -Body $body
            }
            catch {
                Write-Error ($patch | ConvertTo-Json -Depth 100)
                $_
            }
           
        }
        else 
        {
            Write-Error "Could not update widget:"
            Write-Debug $_
        }
    }
}

 

But unfortunately, it seems that a clone doesn't retain the original shape-ids. Thus all links are broken after updating all board id's 😞


@Jesse Houwing  You’ve nailed it. 

Two related comments: 

There’s actually a wish list for what you describe. Head there and upvote it. 

https://community.miro.com/wish-list-32/make-all-internal-miro-links-relational-by-default-736

https://community.miro.com/developer-platform-forum-57/linking-to-inboard-object-using-sdk-672

 

Not telling you anything you didn’t just discover here, but documentation sake…  

The instantiation of a widget in a board, when pasted, or created via API, SDK or user interface, generates a new “id” -- and the internal link works as a global/external link as well by virtue of pointing back to that widget, on that board, with that ID.  

Seems Miro would have to run logic on the paste event, asking users if they want to ‘update internal links so that they be ‘relative to the current board’’, if yes, then scan for the internal links and then rewrite the targets in those links based on the new ids. 


 

Seems Miro would have to run logic on the paste event, asking users if they want to ‘update internal links so that they be ‘relative to the current board’’, if yes, then scan for the internal links and then rewrite the targets in those links based on the new ids. 

For copy/paste, I can see this being problematic, but for a board backup/restore or a “duplicate board”, it feel it’s a different story. There links rhould just work.


@Jesse Houwing  Interesting distinction. 
exploring a bit further … on a board backup, where the internal links re-link to remain relative to the board:   

  • External (out to websites) links: would remain linked externally?
  • Can you think of an instance where you would not want internal links to be relative and re-link?
  • If so, in those instances is it a mix of desires(I want some internal links to relink and some to not?) or is it all/none?  

It may have something to do with how our boards are linking. My co-workers try to make it visually appealing, so we have a text-link in the shape to jump to other elements, not these little [➡️] buttons.

My expectation would be:

  • If the link was to self, I want them to still point to self when a board is duplicated, moved to another account, restored from backup or when a template is inserted and the element being pointed to is part of the template. 
  • In all other cases, just leave the link alone.

It also depends on what you’re calling an “internal link”, in many cases we also provide links from one board to another (like a main board and breakout boards, for classes that have low-bandwidth participants), I’d expect those ti remain untouched. Maybe when cloning a set of boards, that may change as well, but is beyond the scope of my initial request 😉.

 


I’m trying to extend my existing script to find the most likely candidate, which I can as long as I have access to both boards, and then updating the links. straight after clone, I should be able to find the elements based on comparison of a number of fields to try and find the link candidate. I’m guessing that would solve my problem in 98% of the cases.


Reply