Export parsed MR suggestions in the discussions API

Problem

We don't expose parsed suggestions through our public API. Our private API meant for the web MR review only already exports suggestions. You can see the payload for this comment here:

internal API payload for a note with suggestions
{
    "id": "754841236",
    "type": "DiffNote",
    "attachment": null,
    "author": {
        "id": 3457201,
        "username": "viktomas",
        "name": "Tomas Vik",
        "state": "active",
        "avatar_url": "https://secure.gravatar.com/avatar/6042a9152ada74d9fb6a0cdce895337e?s=80&d=identicon",
        "status_tooltip_html": null,
        "show_status": false,
        "availability": null,
        "path": "/viktomas"
    },
    "created_at": "2021-12-07T11:53:59.212Z",
    "updated_at": "2021-12-08T11:56:06.725Z",
    "system": false,
    "noteable_id": 93958054,
    "noteable_type": "MergeRequest",
    "commit_id": null,
    "position": {
        "base_sha": "5e6dffa282c5129aa67cd227a0429be21bfdaf80",
        "start_sha": "5e6dffa282c5129aa67cd227a0429be21bfdaf80",
        "head_sha": "f9ce7e16e56c162edbc9e480108041cf6b0291fe",
        "old_path": "test.js",
        "new_path": "test.ts",
        "position_type": "text",
        "old_line": null,
        "new_line": 14,
        "line_range": {
            "start": {
                "line_code": "5f7b6aabace88f74752a2b616628e855914e4bc7_15_13",
                "type": "new",
                "old_line": null,
                "new_line": 13
            },
            "end": {
                "line_code": "5f7b6aabace88f74752a2b616628e855914e4bc7_15_14",
                "type": "new",
                "old_line": null,
                "new_line": 14
            }
        }
    },
    "resolvable": true,
    "resolved": false,
    "resolved_by": null,
    "resolved_at": null,
    "confidential": false,
    "noteable_iid": 7,
    "commands_changes": {},
    "note": "Adding a line1\n\n```suggestion:-1+0\nfunction containingFunctionABC(): void{\n    function subFunctionDEF(): void{\n```\n```suggestion:-0+0\n    function subFunctionAloha(): void{\n```\n\nAnd here",
    "note_html": "<p data-sourcepos=\"1:1-1:14\" dir=\"auto\">Adding a line1</p>\n<pre data-sourcepos=\"3:1-6:3\" class=\"code highlight js-syntax-highlight language-suggestion\" lang=\"suggestion\" data-lang-params=\"-1+0\" v-pre=\"true\"><code class=\"js-render-suggestion\"><span id=\"LC1\" class=\"line\" lang=\"suggestion\">function containingFunctionABC(): void{</span>\n<span id=\"LC2\" class=\"line\" lang=\"suggestion\">    function subFunctionDEF(): void{</span></code></pre>\n<pre data-sourcepos=\"7:1-9:3\" class=\"code highlight js-syntax-highlight language-suggestion\" lang=\"suggestion\" data-lang-params=\"-0+0\" v-pre=\"true\"><code class=\"js-render-suggestion\"><span id=\"LC1\" class=\"line\" lang=\"suggestion\">    function subFunctionAloha(): void{</span></code></pre>\n<p data-sourcepos=\"11:1-11:8\" dir=\"auto\">And here</p>",
    "last_edited_at": "2021-12-08T11:56:06.688Z",
    "last_edited_by": {
        "id": 3457201,
        "username": "viktomas",
        "name": "Tomas Vik",
        "state": "active",
        "avatar_url": "https://secure.gravatar.com/avatar/6042a9152ada74d9fb6a0cdce895337e?s=80&d=identicon",
        "show_status": false,
        "path": "/viktomas"
    },
    "current_user": {
        "can_edit": true,
        "can_award_emoji": true,
        "can_resolve": true,
        "can_resolve_discussion": true
    },
    "suggestions": [
        {
            "id": 1575097,
            "appliable": true,
            "applied": false,
            "diff_lines": [
                {
                    "line_code": null,
                    "type": "match",
                    "old_line": null,
                    "new_line": null,
                    "text": "@@ -13 +13",
                    "meta_data": {
                        "old_pos": 13,
                        "new_pos": 13
                    },
                    "rich_text": "@@ -13 +13",
                    "can_receive_suggestion": true
                },
                {
                    "line_code": null,
                    "type": "old",
                    "old_line": 13,
                    "new_line": null,
                    "text": "-function containingFunction(): void{",
                    "meta_data": null,
                    "rich_text": "-function containingFunction(): void{",
                    "can_receive_suggestion": false
                },
                {
                    "line_code": null,
                    "type": "old",
                    "old_line": 14,
                    "new_line": null,
                    "text": "-    function subFunctionA(): void{",
                    "meta_data": null,
                    "rich_text": "-    function subFunction<span class=\"idiff left right deletion\">A</span>(): void{",
                    "can_receive_suggestion": false
                },
                {
                    "line_code": null,
                    "type": "new",
                    "old_line": null,
                    "new_line": 13,
                    "text": "+function containingFunctionABC(): void{",
                    "meta_data": null,
                    "rich_text": "+function containingFunction<span class=\"idiff left right addition\">ABC</span>(): void{",
                    "can_receive_suggestion": true
                },
                {
                    "line_code": null,
                    "type": "new",
                    "old_line": null,
                    "new_line": 14,
                    "text": "+    function subFunctionDEF(): void{",
                    "meta_data": null,
                    "rich_text": "+    function subFunction<span class=\"idiff left right addition\">DEF</span>(): void{",
                    "can_receive_suggestion": true
                }
            ],
            "current_user": {
                "can_apply": true
            },
            "inapplicable_reason": null
        },
        {
            "id": 1575098,
            "appliable": true,
            "applied": false,
            "diff_lines": [
                {
                    "line_code": null,
                    "type": "match",
                    "old_line": null,
                    "new_line": null,
                    "text": "@@ -14 +14",
                    "meta_data": {
                        "old_pos": 14,
                        "new_pos": 14
                    },
                    "rich_text": "@@ -14 +14",
                    "can_receive_suggestion": true
                },
                {
                    "line_code": null,
                    "type": "old",
                    "old_line": 14,
                    "new_line": null,
                    "text": "-    function subFunctionA(): void{",
                    "meta_data": null,
                    "rich_text": "-    function subFunctionA(): void{",
                    "can_receive_suggestion": false
                },
                {
                    "line_code": null,
                    "type": "new",
                    "old_line": null,
                    "new_line": 14,
                    "text": "+    function subFunctionAloha(): void{",
                    "meta_data": null,
                    "rich_text": "+    function subFunctionA<span class=\"idiff left right addition\">loha</span>(): void{",
                    "can_receive_suggestion": true
                }
            ],
            "current_user": {
                "can_apply": true
            },
            "inapplicable_reason": null
        }
    ],
    "is_noteable_author": true,
    "discussion_id": "b32eeffef21ab2166715e48834e565b96cda6dd6",
    "emoji_awardable": true,
    "award_emoji": [],
    "report_abuse_path": "/-/abuse_reports/new?ref_url=https%3A%2F%2Fgitlab.com%2Fviktomas%2Ftest-project%2F-%2Fmerge_requests%2F7%23note_754841236&user_id=3457201",
    "noteable_note_url": "https://gitlab.com/viktomas/test-project/-/merge_requests/7#note_754841236",
    "resolve_path": "/viktomas/test-project/-/merge_requests/7/discussions/b32eeffef21ab2166715e48834e565b96cda6dd6/resolve",
    "resolve_with_issue_path": "/viktomas/test-project/-/issues/new?discussion_to_resolve=b32eeffef21ab2166715e48834e565b96cda6dd6&merge_request_to_resolve_discussions_of=7",
    "cached_markdown_version": 1900549,
    "human_access": "Maintainer",
    "is_contributor": false,
    "project_name": "test project",
    "toggle_award_path": "/viktomas/test-project/notes/754841236/toggle_award_emoji",
    "path": "/viktomas/test-project/notes/754841236"
}

When the suggestion is not exported through API, the clients can't:

  • render the suggestion completely unless they recreate the logic that combines the original file, multiline comment and the suggestion snippet to create the full suggestion diff
  • know whether the suggestion has been applied or can be applied

Proposal

Expose parsed suggestions through the public API (mainly GraphQL, but REST would be nice as well).