Add taskListToggle to work item description widget input

What does this MR do and why?

Part 1/2 of Toggling an issue task item checkbox updates th... (#582358). The stacked MR Add taskListToggle to work item description wid... (!244520 - merged) needs to follow this one by at least a milestone, since this provides the GraphQL backing for that.

Task list items, like these —

  • hello
  • yes
  • wonderful

— can be updated "live" by just clicking them, right in the rendered description.

MRs and Ye Olde Issues implement this in a clever way: they send information to the backend, identifying what line of the description the checkbox was on, what we're doing to it (checking, unchecking), and including the whole source line itself (e.g. * [x] wonderful) so the backend can be sure nothing's changed since the request was received. The backend would then mechanically modify the source line and the rendered HTML (without having to re-run the whole thing through the Banzai renderer, which is quite neat), save it to database, and return the result to the user as the result of the API call.

This has some very nice properties. In particular, if two different users click task list items in the same description at the same time, it Just Works™: backend processes will receive each request, fetch, modify, and save the description atomically (and if they happen in absolute parallel (rare), or if they try to both do the same thing to the same checkbox, one of the two transactions will rollback, meaning they'll get a nice error and can retry (or not, if the other change was what they were trying to do anyway!)).

This extends even to more complex scenarios: if a user checks a checkbox while another user modifies the description wholesale, then either (a) the description change didn't change the checkbox's position in the document, and both changes go through without a hitch, or (b) it did, and the checkbox change says "try again!" (and just clicking it again should work, as the new description should land by ActionCable pretty much concurrently), or (c) they happened truly in parallel (rare), and one of the two gets a rollback per above.

Work items have not had this capability. Checkbox toggles have worked by doing the Markdown source rewrite on the frontend, and then updating the whole description field. This means we instead get either (a) one user's change goes through, and then the other user's change overwrites the first one (you see your checkbox toggle, and then you see it toggle back and the other user's toggle), or (b) one of the two transactions gets rolled back (rare). The backend can't combine the changes, as they simply arrive as two separate full description updates.

This MR set uses the existing update_task capability of IssuableBaseService, extending the work item description widget to accept either a description or a task item locator. This is all introduced under the work_items_task_list_toggle FF.

The first MR sets the backend up, and the second MR uses it from the frontend. See the second MR for the before-and-after videos showing it in action :)

How to set up and validate locally

This MR adds backend capabilities only, and so while you can enable the FF and then manually poke the API, it's honestly much easier to check out the second MR and test its capabilities through its live repro on-page instead.

But, if you're interested:

  1. Check out the MR, enable the work_items_task_list_toggle FF.
  2. Create a WI with a description like this:
    Hello, worl!
    
    - [ ] delicious
    - [x] byutiful
    
    Yey.
  3. Grab the work item's global ID (say, from one of the graphql XHRs). Mine's gid://gitlab/WorkItem/697.
  4. Head to the GraphQL explorer at /-/graphql-explorer, and chuck in something like this:
    mutation {
      workItemUpdate(input: {
        id: "gid://gitlab/WorkItem/697",
        descriptionWidget: {
          taskListToggle: {
            checked: true,
            lineSource: "- [ ] delicious",
            lineSourcepos: "3:4-3:4"
          }
        }
      }) {
        workItem {
          description
        }
        errors
      }
    }
    You'll want to replace the id. Note that I'm requesting the backend checks (checked: true) the checkbox whose content (either a space or x) is at line 3, column 4 (3:4-3:4), and I include the full source of line 3. The backend won't make the change if line 3 at the time it receives the request no longer matches that.
  5. Run the query. You should get a response like this:
    {
      "data": {
        "workItemUpdate": {
          "workItem": {
            "description": "Hello, worl!\n\n- [x] delicious\n- [x] byutiful\n\nYey."
          },
          "errors": []
        }
      },
      "correlationId": "01KX5B0148Z0MKZ1071MRQB1BT"
    }
  6. Bonza. Notice that the third line now reads - [x] delicious. If you had the work item open in a tab, you'd notice the checkbox is magically checked.
  7. If you retry the same request, you'll get the same result with an error attached: this is because line 3 no longer matches what you said it should look like.
  8. Try changing lineSourcepos to 4:4-4:4, checked to false, and lineSource to - [x] byutiful. It should uncheck the second checkbox.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Asherah Connor

Merge request reports

Loading