Skip to content

Commit 8d322d3

Browse files
feat: add parentId support for threaded comment replies (#407) (#415)
- Add parentId parameter to AddComment interface in version2 and version3 - Include parentId in request body for addComment method in both API versions - Update CHANGELOG.md with enhancement entries crediting @lukiffer This allows users to reply to existing comments by providing the parentId of the comment they want to reply to, enabling threaded comment functionality.
1 parent f2009b0 commit 8d322d3

37 files changed

Lines changed: 906 additions & 903 deletions

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
* Updated dependencies
88
* Improved JSDoc coverage and quality
9+
* Refactored integration tests to use global setup/teardown for better performance and reliability
10+
* Wrapped all integration tests in describe blocks for better organization
911

1012
### Bug Fixes
1113

@@ -44,6 +46,11 @@
4446
* **WorkflowSchemes**
4547
* Added `WorkflowSchemes.switchWorkflowSchemeForProject`
4648

49+
#### Enhancements
50+
51+
* **IssueComments**
52+
* Added `parentId` parameter to `IssueComments.addComment` to support threaded comment replies. Thanks to [lukiffer](https://github.com/lukiffer) for requesting this feature ([#407](https://github.com/MrRefactoring/jira.js/issues/407)).
53+
4754
#### Deprecations
4855

4956
* Marked `JiraExpressions.evaluateJiraExpression` as deprecated
@@ -90,6 +97,11 @@
9097
* **WorkflowSchemes**
9198
* Added `WorkflowSchemes.switchWorkflowSchemeForProject`
9299

100+
#### Enhancements
101+
102+
* **IssueComments**
103+
* Added `parentId` parameter to `IssueComments.addComment` to support threaded comment replies. Thanks to [lukiffer](https://github.com/lukiffer) for requesting this feature ([#407](https://github.com/MrRefactoring/jira.js/issues/407)).
104+
93105
#### Deprecations
94106

95107
* Marked `JiraExpressions.evaluateJiraExpression` as deprecated

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
"doc": "typedoc --name \"Jira.js - Jira Cloud API library\" --out docs ./src/index.ts --favicon https://bad37fb3-cb50-4e0b-9035-a3e09e8afb3b.selstorage.ru/jira.js%2Ffavicon.svg",
9595
"test": "pnpm run build:tests && pnpm run test:unit && pnpm run test:integration",
9696
"test:unit": "vitest run tests/unit --sequence.concurrent",
97-
"test:integration": "vitest run tests/integration --bail=1 --no-file-parallelism --max-concurrency 1 -c vitest.config.mts --hookTimeout 100000 --testTimeout 100000",
97+
"test:integration": "VITEST_MODE=integration vitest run tests/integration --bail=1 --no-file-parallelism --max-concurrency 1 --hookTimeout 100000 --testTimeout 100000",
9898
"replace:all": "pnpm run replace:fixExpansionMarkup && pnpm run replace:permissions:version2 && pnpm run replace:permissions:version3 && pnpm run replace:pagination:version2 && pnpm run replace:pagination:version3 && pnpm run replace:async:version2 && pnpm run replace:async:version3 && pnpm run replace:expansion:version2 && pnpm run replace:expansion:version3 && pnpm run replace:ordering:version2 && pnpm run replace:ordering:version3 && pnpm run replace:groupMember:version2 && pnpm run replace:workflowPaginated:version2 && pnpm run replace:attachment:serviceDesk && pnpm run replace:priority:version3 && pnpm run replace:projectAvatar:version3 && pnpm run replace:issueType:version3 && pnpm run replace:issueType:version2 && pnpm run replace:projectAvatar:version2 && pnpm run replace:priority:version2 && pnpm run replace:projectCreate:agile && pnpm run replace:filterCreate:agile",
9999
"replace:permissions:version2": "grep -rl \"(#permissions)\" ./src/version2 | xargs sed -i '' 's/(#permissions)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v2\\/intro\\/#permissions)/g'",
100100
"replace:permissions:version3": "grep -rl \"(#permissions)\" ./src/version3 | xargs sed -i '' 's/(#permissions)/(https:\\/\\/developer.atlassian.com\\/cloud\\/jira\\/platform\\/rest\\/v3\\/intro\\/#permissions)/g'",

src/version2/issueComments.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export class IssueComments {
158158
id: parameters.id,
159159
jsdAuthorCanSeeRequest: parameters.jsdAuthorCanSeeRequest,
160160
jsdPublic: parameters.jsdPublic,
161+
parentId: parameters.parentId,
161162
properties: parameters.properties,
162163
renderedBody: parameters.renderedBody,
163164
self: parameters.self,

src/version2/parameters/addComment.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ export interface AddComment extends Comment {
99
* rendered in HTML.
1010
*/
1111
expand?: string;
12+
/** The ID of the comment to which you're replying. */
13+
parentId?: string;
1214
}

src/version3/issueComments.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ export class IssueComments {
172172
id: parameters.id,
173173
jsdAuthorCanSeeRequest: parameters.jsdAuthorCanSeeRequest,
174174
jsdPublic: parameters.jsdPublic,
175+
parentId: parameters.parentId,
175176
properties: parameters.properties,
176177
renderedBody: parameters.renderedBody,
177178
self: parameters.self,

src/version3/parameters/addComment.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ export interface AddComment extends Omit<Comment, 'body'> {
1414
* Format](https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/).
1515
*/
1616
comment?: string | Document;
17+
/** The ID of the comment to which you're replying. */
18+
parentId?: string;
1719
}
Lines changed: 55 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,82 @@
1-
import { afterAll, beforeAll, test } from 'vitest';
1+
import { describe, test } from 'vitest';
22
import type { AgileModels } from '@jirajs';
33
import { Constants } from '@tests/integration/constants';
44
import {
5-
createAgileProject,
6-
deleteAgileProject,
75
getAgileClient,
86
getVersion3Client,
97
} from '@tests/integration/utils';
108

11-
const client = getAgileClient();
9+
describe.sequential('Sprint', () => {
10+
const client = getAgileClient();
1211

13-
let board: AgileModels.Board | undefined;
14-
let sprint: AgileModels.Sprint;
12+
let board: AgileModels.Board | undefined;
13+
let sprint: AgileModels.Sprint;
1514

16-
beforeAll(async () => {
17-
await createAgileProject();
18-
});
19-
20-
afterAll(async () => {
21-
await deleteAgileProject();
22-
});
23-
24-
test.sequential.skip('should create new sprint', async ({ expect }) => {
25-
const boards = await client.board.getAllBoards({ name: Constants.testAgileProjectKey });
15+
test.sequential.skip('should create new sprint', async ({ expect }) => {
16+
const boards = await client.board.getAllBoards({ name: Constants.testAgileProjectKey });
2617

27-
expect(boards.total).toBe(1);
18+
expect(boards.total).toBe(1);
2819

29-
[board] = boards.values;
20+
[board] = boards.values;
3021

31-
// @ts-expect-error Wrong typings
32-
sprint = await client.sprint.createSprint({
33-
name: 'New sprint',
3422
// @ts-expect-error Wrong typings
35-
originBoardId: board.id,
23+
sprint = await client.sprint.createSprint({
24+
name: 'New sprint',
25+
// @ts-expect-error Wrong typings
26+
originBoardId: board.id,
27+
});
28+
29+
expect(!!sprint).toBeTruthy();
30+
expect(sprint.name).toBe('New sprint');
31+
expect(sprint.state).toBe('future');
3632
});
3733

38-
expect(!!sprint).toBeTruthy();
39-
expect(sprint.name).toBe('New sprint');
40-
expect(sprint.state).toBe('future');
41-
});
42-
43-
test.sequential.skip('should create and move task to sprint', async ({ expect }) => {
44-
const issue = await getVersion3Client().issues.createIssue({
45-
fields: {
46-
summary: 'Test task',
47-
project: {
48-
key: Constants.testAgileProjectKey,
34+
test.sequential.skip('should create and move task to sprint', async ({ expect }) => {
35+
const issue = await getVersion3Client().issues.createIssue({
36+
fields: {
37+
summary: 'Test task',
38+
project: {
39+
key: Constants.testAgileProjectKey,
40+
},
41+
issuetype: {
42+
name: 'Task',
43+
},
4944
},
50-
issuetype: {
51-
name: 'Task',
52-
},
53-
},
54-
});
45+
});
5546

56-
await client.backlog.moveIssuesToBacklog({
57-
issues: [issue.key],
58-
});
47+
await client.backlog.moveIssuesToBacklog({
48+
issues: [issue.key],
49+
});
50+
51+
await client.sprint.moveIssuesToSprintAndRank({
52+
sprintId: sprint.id,
53+
issues: [issue.key],
54+
});
5955

60-
await client.sprint.moveIssuesToSprintAndRank({
61-
sprintId: sprint.id,
62-
issues: [issue.key],
56+
expect(!!issue).toBeTruthy();
6357
});
6458

65-
expect(!!issue).toBeTruthy();
66-
});
59+
test.sequential.skip('should return issues for sprint', async ({ expect }) => {
60+
const { issues } = await client.sprint.getIssuesForSprint({
61+
sprintId: sprint.id,
62+
});
6763

68-
test.sequential.skip('should return issues for sprint', async ({ expect }) => {
69-
const { issues } = await client.sprint.getIssuesForSprint({
70-
sprintId: sprint.id,
64+
expect(!!issues).toBeTruthy();
65+
expect(issues[0].fields?.summary).toBe('Test task');
7166
});
7267

73-
expect(!!issues).toBeTruthy();
74-
expect(issues[0].fields?.summary).toBe('Test task');
75-
});
68+
test.sequential.skip('should partially update sprint', async ({ expect }) => {
69+
const newSprint = await client.sprint.partiallyUpdateSprint({
70+
sprintId: sprint.id,
71+
state: 'active',
72+
startDate: new Date(),
73+
endDate: new Date(Date.now() + 1000),
74+
});
7675

77-
test.sequential.skip('should partially update sprint', async ({ expect }) => {
78-
const newSprint = await client.sprint.partiallyUpdateSprint({
79-
sprintId: sprint.id,
80-
state: 'active',
81-
startDate: new Date(),
82-
endDate: new Date(Date.now() + 1000),
76+
expect(newSprint.state).toBe('active');
8377
});
8478

85-
expect(newSprint.state).toBe('active');
86-
});
87-
88-
test.sequential.skip('should remove sprint', async () => {
89-
await client.sprint.deleteSprint({ sprintId: sprint.id });
79+
test.sequential.skip('should remove sprint', async () => {
80+
await client.sprint.deleteSprint({ sprintId: sprint.id });
81+
});
9082
});

tests/integration/setup.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import 'dotenv/config';
2+
import { createAgileProject } from './utils/createAgileProject';
3+
import { createSoftwareProject } from './utils/createSoftwareProject';
4+
import { deleteAgileProject } from './utils/deleteAgileProject';
5+
import { deleteSoftwareProject } from './utils/deleteSoftwareProject';
6+
7+
export default async function setup() {
8+
console.log('Setting up integration test environment...');
9+
10+
try {
11+
await deleteSoftwareProject();
12+
console.log('[1/4] Cleaned up existing software project');
13+
} catch {
14+
console.error('[1/4] No existing software project to clean up');
15+
}
16+
17+
try {
18+
await deleteAgileProject();
19+
console.log('[2/4] Cleaned up existing agile project');
20+
} catch {
21+
console.error('[2/4] No existing agile project to clean up');
22+
}
23+
24+
try {
25+
await createSoftwareProject();
26+
console.log('[3/4] Software project created successfully');
27+
} catch (error) {
28+
console.error('[3/4] Failed to create software project:', error);
29+
throw error;
30+
}
31+
32+
try {
33+
await createAgileProject();
34+
console.log('[4/4] Agile project created successfully');
35+
} catch (error) {
36+
console.error('[4/4] Failed to create agile project:', error);
37+
try {
38+
await deleteSoftwareProject();
39+
} catch (cleanupError) {
40+
console.error('Failed to cleanup software project:', cleanupError);
41+
}
42+
throw error;
43+
}
44+
45+
console.log('Integration test environment setup complete');
46+
}

tests/integration/teardown.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import 'dotenv/config';
2+
import { deleteAgileProject } from './utils/deleteAgileProject';
3+
import { deleteSoftwareProject } from './utils/deleteSoftwareProject';
4+
5+
export default async function teardown() {
6+
console.log('Tearing down integration test environment...');
7+
8+
const errors: Error[] = [];
9+
10+
try {
11+
await deleteSoftwareProject();
12+
console.log('[1/2] Software project deleted successfully');
13+
} catch (error) {
14+
console.error('[1/2] Failed to delete software project:', error);
15+
errors.push(error as Error);
16+
}
17+
18+
try {
19+
await deleteAgileProject();
20+
console.log('[2/2] Agile project deleted successfully');
21+
} catch (error) {
22+
console.error('[2/2] Failed to delete agile project:', error);
23+
errors.push(error as Error);
24+
}
25+
26+
if (errors.length > 0) {
27+
console.error('Some projects could not be deleted:', errors);
28+
}
29+
30+
console.log('Integration test environment teardown complete');
31+
}

tests/integration/utils/cleanupEnvironment.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)