Power UP t.set('board', 'shared', k, v) always returns 409

Browser affected: Chrome
Third party cookies enabled: yes
Extensions disabled: yes

I have an odd scenario where calls to t.set('board', 'shared', obj) are consistently failing with a 409 response:

This call to t.set is made after receiving a response from our API. It is the only call made to set shared data. The other response from pluginData in the screenshot above is a successful t.set('member', 'private') call.

Writing shared board data is the only thing that seems to be affected. The shared values persist in memory, but upon refresh they are gone. I am guessing because of the 409 received prior.

Oddly enough, I can get the shared values to stick when calling in response to a button click in the power up. When I make them in response to a successful post request to our API it seems to fail.

Yeah, still seeing this. Seems like a real bug, especially that there is no guidance on when this api should or shouldn’t be called. Please advise.

hi, i’m adriana and i’m a developer with trello.

t.set() is a wrapper around a ‘upsert’ function which makes an API call to the pluginData endpoint, with either a POST if there is no data already in the database, or a PUT if there is already data. I believe the issue is that we are sending POST requests even though we should be sending PUT requests, thus resulting in the unexpected 409.

can you explain more of the context of t.set() call? once we know more about how that function is called, we can try to dig in to the data conflict.

Thanks @Adriana

This issue is happening for a new shared value. We have the following function:

function autoSyncBoard(companyId) {
  t.board('id', 'name')
    .then(board =>
      createProjectForBoard(companyId, board)
        .then(project => {
          trackSegmentEvent('trello_sync_set', {
            method: 'auto',
          });

          return syncProjectToBoard(t, project.id);
        })
        .then(() => syncDateOverlay(t))
    )
    .catch(error => {
      autoSyncWarning.text(error.message);
    });
}

This function is called after the initial setup of our power-up and in response to a user interaction. We are effectively trying to set a shared value inside this process:

function autoSyncBoard(companyId) {
  t.board('id', 'name')
    .then(board =>
      createProjectForBoard(companyId, board)
        .then(project => {
          trackSegmentEvent('trello_sync_set', {
            method: 'auto',
          });

          // ATTEMPTING TO USE t.set('board', 'shared', 'key', 'value') HERE
          t.set('board', 'shared', 'my-key', 'my-value'); // this always fails with the conflict

          return syncProjectToBoard(t, project.id);
        })
        .then(() => syncDateOverlay(t))
    )
    .catch(error => {
      autoSyncWarning.text(error.message);
    });
}

could you please describe the user interaction that is triggering this call? i’m working on trying to reproduce your error and this information would be helpful. i am not able to reproduce this error using a board button.

This happens in response to a button click from our own markup.

<button class="sync-options__option__button" id="autoSyncButton">Select auto</button>

autoSyncButton.on('click', event => {
  if (hasRestrictedCompanies || allowedCompanies.length > 0) {
    const selectCompany = (t2, companyId) => {
      t2.closePopup();
      autoSyncBoard(companyId);
    };
    const items = allowedCompanies.map(c => ({
      text: c.name,
      callback: t2 => selectCompany(t2, c.id),
    }));

    t.popup({
      mouseEvent: event,
      title: 'Where should this board sync to?',
      items,
    });

    return;
  }
  autoSyncBoard(allowedCompanies[0].id);
});

There doesn’t seem to be any noticeable reason why this should be failing, and I don’t think I can give an exhaustive treatment of everything that transpires, but writing a shared value inside this whole processes ALWAYS fails.

okay, i think i understand the usage of this function in your code. thanks for your clarification.

when i made a call t.set('board', 'shared', 'my-key', 'my-value'), the request payload has value: "{\"my-key\":\"my-value\"}". however, in the screenshot you shared, the value has two key value pairs, indicating that there already was pluginData on the shared board level. the client should have run a PUT request instead of a POST, though i’m having some difficulty in figuring out why the wrong method was used.

i wonder if there might be some sort of corrupted data or schema that is messing up the web client logic. could you share the results of going to https://trello.com/1/boards/<shortUrl>/plugindata? feel free to anonymize any data as you see fit.

otherwise, here’s some other questions that would provide more information:

  • have you tested this situation on a fresh board, i.e. on a board that has not had this plugin enabled? what are the results if you try it?
  • have you enabled/disabled this plugin on the board recently?
  • is the correct data returned when you run ‘t.get’ on your specific key? can you run ‘t.remove’?

thanks for your patience as i work on this. i can’t reproduce this error locally, so i’m having to rely on your information to try to debug it. :slight_smile:

@Adriana thank you.

To answer your questions:

  1. I tested this on a fresh board, and it works as expected (initially)*
  2. Part of our local development process involves enabling/disabling frequently on a single board
  3. Correct data is returned on the fresh board

The plugindata url shows the correct data on the fresh board, but the shared data is never returned on the existing board.

  • The fresh board only works the first time I set shared data. All subsequent attempts (after disabling/enabling the power up again) return me to the 409 state:

My plugindata on the fresh board is as follows:

[
  {
    "id": "63232d2564a6db01c7bcbc1d",
    "idPlugin": "62b35ba7241dd24ee012058d",
    "scope": "board",
    "idModel": "63232d09068c5000bc677041",
    "value": "{\"helloWorld\":\"shared-data\",\"syncProjectId\":920885,\"syncDates\":true}",
    "access": "shared",
    "dateLastUpdated": "2022-09-15T13:48:21.981Z"
  }
]

The helloWorld value is set in the aforementioned autoSyncBoard function in a very simple manner:

function autoSyncBoard(companyId) {
  t.board('id', 'name')
    .then(board =>
      createProjectForBoard(companyId, board)
        .then(project => {
          trackSegmentEvent('trello_sync_set', {
            method: 'auto',
          });

          t.set('board', 'shared', 'helloWorld', 'shared-data');

          return syncProjectToBoard(t, project.id);
        })
        .then(() => syncDateOverlay(t))
    )
    .catch(error => {
      autoSyncWarning.text(error.message);
    });
}

The first time I enable the power up on a fresh board, helloWorld is set correctly. Subsequent disabling followed by enabling causes the 409 to return.

Thank you so much for your help

hmm, okay. does the function createProjectForBoard modify board shared data? how are you using the board parameter in this function?

i ask because, if you’ve only set the hellowWorld key in the call as you’ve indicated, it should be the only value in the pluginData when you fetch that data on a fresh board. since there are multiple key/value pairs, i’m guessing the createProjectForBoard function also creates shared board data. is that guess correct?

The syncProjectToBoard function also sets shared data. A modal via t.modal is spawned by syncDateOverlay(t) That modal has a button that sets shared data as well.

The respective functions are:

// projectId is a number type
export function setSyncedProjectId(t, projectId) {
  return t.set('board', 'shared', SYNC_PROJECT_ID_KEY, projectId);
}

// syncDates is a boolean
export function updateSyncDates(t, syncDates) {
  return t.set('board', 'shared', SYNC_DATES_KEY, syncDates);
}

If it is helpful, we did try grouping these all into a single t.set to see if it helped, but it did not appear to make a difference.

@BrianScaturro Weird question but I have to rule something out:

When you’re testing all of this, are the plugin, board, and member you are logged in as all part of the same workspace? There is a very obscure bug right now where the plugin data websocket can be disconnected if you are on a board with a private plugin, but you are not a part of that workspace that owns the board and the private plugin.

@BrianScaturro Also you say you are enabling and disabling the Power-Up in between the t.set() calls. Why are you doing that, and do you have any on-enable or on-disable capabilities?

Everything is on the same workspace.

The enabling/disabling is because the feature in question is part of the enable flow. When a user enables the power-up, we collect some info to setup the environment for synchronizing with our service.

If we have on-disable capabilities are present, is it idiomatic to remove shared data as part of that process?

Gotcha, thanks for the replies.

If we have on-disable capabilities are present, is it idiomatic to remove shared data as part of that process?

Hmm, not particularly I think. I’d assume you aren’t though, right?

And to give you a bit of insight about what’s going on with this bug:

The first time you set a shared plugin data on a board (i.e. call t.set('board', 'shared') for the first time on a board), the web client will POST to /pluginData. This creates a new pluginData object for this board with the shared access. You can view the new object by going to the /pluginData route.

Afterwards, attempting to set more shared board plugindata will send a PUT instead, to update the existing pluginData object.

This 409:Conflict error happens because the web client mistakenly tries to POST to pluginData when that pluginData object already exists. It should be doing a PUT to the existing data instead.

We’re currently trying to investigate why this error occurs and if it’s a widespread issue. Thanks for working with us!

I am currently not leveraging on-disable for any sort of cleanup.

Please let me know if I can answer any more questions or try anything else to help troubleshoot.

Thank you so much for looking into things :bowing_man:

hi everyone, we did a bunch of digging on this and it seems that our websockets weren’t configured correctly, so the model that was modified was not receiving updates for pluginData changes. we’ve rolled out a change to our websockets platform that should fix this, and our logs seem to indicate a decrease in this error. please let us know if there are any new problems, and thanks for working with us!

@Adriana @Jireh the problem persists. I am still receiving 409s when trying to set shared data as outlined in this thread

sorry to hear that! i was really hoping that our websockets fix would catch this. :\

let’s try looking at your websockets. you can see the trello websocket in the developer console, under network > WS. can you please send me a screenshot of the request and response headers? you might have to block out a cookie or other sensitive data.

otherwise, you can check your websocket activity (under network > WS > client websocket > messages) as you try setting data. if you aren’t seeing an ‘updateModel’ message when you change power-up data, we know it’s the websockets not sending information correctly.

Hey @BrianScaturro,

We believe we’ve pinpointed the issue. There was a bug that was introduced in a security fix we did relating to plugin data from a while back. We’re working on a fix and will keep you updated. Thank you so much for working with us so far!