microsoft / microsoft/azure-devops-extension-sdk

SDK saving wrong workitem when saving

Open
#113 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
159
Forks
53
PR merge metrics
No merged PRs in 30d

Description

Hi!

We have an extension designed to send the workitem data to an API whenever the user saves it.
But if the user opens another workitem and save it, the SDK still send the old item, not the current one.

This happens in two places:

  1. In the backlogs page:
  • In any project, go to Backlogs, click an item, edit and then save it: it saves correctly;
  • In the same backlog page, open another item and save it: it sends the first workitem, not the current one.
  1. In the child/parents list inside workitems:
  • Open an item that has a parent or child item, edit and save it: it saves correctly;
  • In the same item, open a parent or child item, edit and save it: it still saves the one you first opened.

I’m not sure if it’s actually a bug in ADO or my code, so I’ll put it here for reference.

The extension is a Vue.js project.
App.vue:

try {
    await SDK.init({
      usePlatformScripts: true
    }).then(() => {
      SDK.register('{removed id}', function () { // This register handles a tab inside the workitem. The issue is in the one below
        return {
          onLoaded() {
            const workkitemService = SDK.getService("ms.vss-work-web.work-item-form");
            workkitemService.then(function (service) {
              service.getFieldValue("System.WorkItemType").then(function (value) {
                if (value !== "Task") {
                  wrongItemType.value = true;
                  isLoading.value = false;
                  return;
                }

                loadItemData()
                    .finally(() => isLoading.value = false);
              });
            });
          }
        }
      });

      SDK.register('{another id}', function () { // This is the observer that handles the workitem saving
        return {
          onSaved: function () {
            const workkitemService = SDK.getService("ms.vss-work-web.work-item-form");
            workkitemService.then(function (service) {
              service.getFieldValue("System.WorkItemType").then(function (workType) {
                loadItemData()
                    .then(() => handleSave(workType))
                    .catch(e => {
                      [...]
                    });
              });
            });
          },
        }
      });

      SDK.notifyLoadSucceeded();
    });
  } catch (e) {
    ErrorUtils.handleError(e)
  }

function loadItemData() {
  return new Promise((resolve, reject) => {
    const workkitemService = SDK.getService("ms.vss-work-web.work-item-form");
    workkitemService.then(function (service) {
      service.getId()
          .then(id => {
            service.getWorkItemResourceUrl(id)
                .then(url => {
                  const itemNumber = getItemNumber(url, id);
                  item.value = {
                    numero: itemNumber
                  };

                  const fieldsMap = new Map();
                  fieldsMap.set('System.Title', ['title']);
                  [... a bunch of other fields here]

                  service.getFields()
                      .then(fields => {
                        const promises = fields.map(field => {
                          return service.getFieldValue(field.referenceName)
                              .then(value => {
                                const fieldName = field.referenceName;
                                const fieldTasks = fieldsMap.get(fieldName);
                                if (!fieldTasks) {
                                  return;
                                }

                                fieldTasks.forEach(field => {
                                  if (!value) {
                                    return;
                                  }

                                  if (field === 'parent') {
                                    value = getItemNumber(url, value);
                                  }
                                  item.value[field] = value;
                                });
                              })
                              .catch(e => ErrorUtils.handleError(e));
                        });

                        Promise.all(promises)
                            .then(() => resolve())
                            .catch(e => {
                              ErrorUtils.handleError(e);
                              reject(e);
                            });
                      })
                      .catch(e => {
                        ErrorUtils.handleError(e);
                        reject(e);
                      });
                });
          })
          .catch(e => {
            ErrorUtils.handleError(e);
            reject(e);
          });
    });
  });

This is my vss-extension.json:

{
  "manifestVersion": 1,
  "id": "my-id",
  "publisher": "my-publisher",
  "version": "1.0.1",
  "name": "Name",
  "description": "Description",
  "categories": [
    "Azure Boards"
  ],
  "targets": [
    {
      "id": "Microsoft.VisualStudio.Services"
    }
  ],
  "files": [
    {
      "path": "public",
      "addressable": true
    },
    {
      "path": "dist",
      "addressable": true
    }
  ],
  "scopes": [
    "vso.work"
  ],
  "contributions": [
    {
      "id": "my-tab-workitem-id",
      "type": "ms.vss-work-web.work-item-form-page",
      "description": "Description",
      "targets": [
        "ms.vss-work-web.work-item-form"
      ],
      "properties": {
        "name": "Name",
        "uri": "dist/index.html"
      }
    },
    {
      "id": "my-observer-id",
      "type": "ms.vss-work-web.work-item-notifications",
      "description": "Observe work item changes",
      "targets": [
        "ms.vss-work-web.work-item-form"
      ],
      "properties": {
        "uri": "dist/index.html"
      }
    }
  ]
}

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with App.vue, especially the work-item observer's onSaved callback and loadItemData, then trace each SDK.getService call and service.getId() lookup while switching between work items in the two reported contexts. Done means the saved item data corresponds to the currently opened work item in both backlogs and parent/child lists, with the behavior verified against the reproduction steps.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
devtools, frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.