Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Added missing labels for PR merge strategies(AXON-1733)
- Fixed authentication error
- Fixed user mentions not working in Jira issue comments when using editor

## What's new in 4.0.14

Expand Down
49 changes: 49 additions & 0 deletions src/webviews/components/issue/common/adfToWikimarkup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,55 @@ describe('adfToWikimarkup', () => {
expect(hasNullLocalId).toBe(false);
});

it('should remove all null and undefined values from attrs', () => {
// WikiMarkupTransformer creates mentions with null values like localId, userType
const wikimarkup = '[~accountid:test-user-id]';
const result = convertWikimarkupToAdf(wikimarkup);
const resultStr = JSON.stringify(result);
expect(resultStr).not.toContain(':null');
expect(resultStr).not.toContain(':undefined');
});

it('should remove accountid: prefix from mention id', () => {
const wikimarkup = '[~accountid:abc123def456]';
const result = convertWikimarkupToAdf(wikimarkup);
const resultStr = JSON.stringify(result);
// Should have the id without prefix
expect(resultStr).toContain('"id":"abc123def456"');
// Should NOT have the prefix
expect(resultStr).not.toContain('accountid:abc123def456');
});

it('should handle mention with text in wikimarkup format', () => {
const wikimarkup = 'Hello [~accountid:user-abc] how are you?';
const result = convertWikimarkupToAdf(wikimarkup);
expect(result.version).toBe(1);
expect(result.type).toBe('doc');
// Check that mention node exists and id is cleaned
const resultStr = JSON.stringify(result);
expect(resultStr).toContain('"type":"mention"');
expect(resultStr).toContain('"id":"user-abc"');
});

it('should produce valid mention structure for Jira API v3', () => {
// This test documents the exact ADF structure required by Jira API v3
const wikimarkup = '[~accountid:user-id-123]';
const result = convertWikimarkupToAdf(wikimarkup);

// Find the mention node in the ADF
const paragraph = result.content?.[0];
const mentionNode = paragraph?.content?.find((node: any) => node.type === 'mention');

expect(mentionNode).toBeDefined();
expect(mentionNode!.type).toBe('mention');
// ID must NOT have 'accountid:' prefix - Jira API rejects it
expect(mentionNode!.attrs!.id).toBe('user-id-123');
expect(mentionNode!.attrs!.id).not.toContain('accountid:');
// Must not have null values - Jira API rejects them
expect(mentionNode!.attrs!.localId).toBeUndefined();
expect(mentionNode!.attrs!.userType).toBeUndefined();
});

it('should handle headings', () => {
const wikimarkup = 'h1. Heading';
const result = convertWikimarkupToAdf(wikimarkup);
Expand Down
27 changes: 20 additions & 7 deletions src/webviews/components/issue/common/adfToWikimarkup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ export function convertAdfToWikimarkup(adf: AdfNode | string | null | undefined)

/**
* Sanitizes ADF by removing invalid attributes that Jira API doesn't accept
* Removes null localId values - Jira API v3 requires either a valid UUID or no localId at all
* Removes null/undefined values from attrs - Jira API v3 doesn't accept them
* Also fixes mention id format - removes 'accountid:' prefix that WikiMarkupTransformer adds
*/
function sanitizeAdf(node: AdfNode): AdfNode {
if (!node || typeof node !== 'object') {
Expand All @@ -117,16 +118,28 @@ function sanitizeAdf(node: AdfNode): AdfNode {

const sanitized: AdfNode = { ...node };

// Remove null or undefined localId attributes - Jira API doesn't accept them
// Remove null/undefined attributes - Jira API doesn't accept them
if (sanitized.attrs) {
if (sanitized.attrs.localId === null || sanitized.attrs.localId === undefined) {
// eslint-disable-next-line no-unused-vars
const { localId, ...restAttrs } = sanitized.attrs;
sanitized.attrs = restAttrs;
const cleanedAttrs = Object.entries(sanitized.attrs).reduce(
(acc, [key, value]) => {
if (value !== null && value !== undefined) {
acc[key] = value;
}
return acc;
},
{} as Record<string, any>,
);

// Strip 'accountid:' prefix from mention id (WikiMarkupTransformer adds it, but API expects plain id)
if (sanitized.type === 'mention' && cleanedAttrs.id && typeof cleanedAttrs.id === 'string') {
cleanedAttrs.id = cleanedAttrs.id.replace(/^accountid:/, '');
}

// If attrs is now empty, remove it entirely
if (Object.keys(sanitized.attrs).length === 0) {
if (Object.keys(cleanedAttrs).length === 0) {
delete sanitized.attrs;
} else {
sanitized.attrs = cleanedAttrs;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export default class PopoutMentionPicker extends React.Component<
components={{ Option: UserOption, DropdownIndicator, IndicatorSeparator: null }}
menuIsOpen
onChange={this.onSelectChange}
loadOptions={this.props.loadUserOptions}
loadOptions={(inputValue: string) => this.props.loadUserOptions(inputValue)}
defaultOptions
placeholder="Search..."
tabSelectsValue={false}
controlShouldRenderValue={false}
Expand Down
Loading