Skip to content

Commit 0cd08cf

Browse files
authored
Merge pull request #340 from kodustech/fix/reaction-cron-queries
update method get prs and reactions
2 parents f683b5f + c392080 commit 0cd08cf

File tree

9 files changed

+625
-486
lines changed

9 files changed

+625
-486
lines changed

packages/kodus-flow/package.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@
6767
"homepage": "https://github.com/kodustech/kodus-ai#readme",
6868
"dependencies": {
6969
"@google/generative-ai": "^0.24.1",
70-
"@modelcontextprotocol/sdk": "^1.19.1",
70+
"@modelcontextprotocol/sdk": "^1.20.2",
7171
"ajv": "^8.17.1",
7272
"ajv-formats": "^3.0.1",
7373
"dotenv": "^17.2.3",
7474
"json5": "^2.2.3",
7575
"mongodb": "^6.20.0",
76-
"openai": "^5.23.2",
77-
"pino": "^10.0.0",
76+
"openai": "^6.7.0",
77+
"pino": "^10.1.0",
7878
"tslib": "^2.8.1",
7979
"zod": "^4.1.12",
8080
"zod-from-json-schema": "^0.5.0"
@@ -83,14 +83,14 @@
8383
"@opentelemetry/api": "^1.9.0",
8484
"@types/ajv": "^1.0.4",
8585
"@types/events": "^3.0.3",
86-
"@types/node": "^24.7.0",
86+
"@types/node": "^24.9.1",
8787
"@types/uuid": "^11.0.0",
88-
"@typescript-eslint/eslint-plugin": "^8.45.0",
89-
"@typescript-eslint/parser": "^8.45.0",
90-
"@vitest/coverage-v8": "^3.2.4",
91-
"@vitest/ui": "^3.2.4",
88+
"@typescript-eslint/eslint-plugin": "^8.46.2",
89+
"@typescript-eslint/parser": "^8.46.2",
90+
"@vitest/coverage-v8": "^4.0.3",
91+
"@vitest/ui": "^4.0.3",
9292
"concurrently": "^9.2.1",
93-
"eslint": "^9.37.0",
93+
"eslint": "^9.38.0",
9494
"eslint-config-prettier": "^10.1.8",
9595
"eslint-plugin-import": "^2.32.0",
9696
"eslint-plugin-prettier": "^5.5.4",
@@ -101,10 +101,10 @@
101101
"tsconfig-paths": "^4.2.0",
102102
"tsup": "^8.5.0",
103103
"typescript": "^5.9.3",
104-
"typescript-eslint": "^8.45.0",
105-
"vite": "^7.1.9",
104+
"typescript-eslint": "^8.46.2",
105+
"vite": "^7.1.12",
106106
"vite-tsconfig-paths": "^5.1.4",
107-
"vitest": "^3.2.4"
107+
"vitest": "^4.0.3"
108108
},
109109
"peerDependencies": {
110110
"@opentelemetry/api": "^1.9.0"

packages/kodus-flow/yarn.lock

Lines changed: 256 additions & 333 deletions
Large diffs are not rendered by default.
Lines changed: 93 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import { IUseCase } from '@/shared/domain/interfaces/use-case.interface';
22
import { Inject, Injectable } from '@nestjs/common';
33
import { OrganizationAndTeamData } from '@/config/types/general/organizationAndTeamData';
4-
import { PullRequestWithFiles } from '@/core/domain/platformIntegrations/types/codeManagement/pullRequests.type';
54
import { CodeManagementService } from '@/core/infrastructure/adapters/services/platformIntegration/codeManagement.service';
6-
import * as moment from 'moment-timezone';
7-
import { DeliveryStatus } from '@/core/domain/pullRequests/enums/deliveryStatus.enum';
5+
import { IPullRequestWithDeliveredSuggestions } from '@/core/domain/pullRequests/interfaces/pullRequests.interface';
86
import {
97
PULL_REQUESTS_SERVICE_TOKEN,
108
IPullRequestsService,
119
} from '@/core/domain/pullRequests/contracts/pullRequests.service.contracts';
12-
import { ICodeReviewFeedback } from '@/core/domain/codeReviewFeedback/interfaces/codeReviewFeedback.interface';
10+
import { PullRequestState } from '@/shared/domain/enums/pullRequestState.enum';
11+
import { PinoLoggerService } from '@/core/infrastructure/adapters/services/logger/pino.service';
1312

1413
@Injectable()
1514
export class GetReactionsUseCase implements IUseCase {
@@ -18,141 +17,142 @@ export class GetReactionsUseCase implements IUseCase {
1817

1918
@Inject(PULL_REQUESTS_SERVICE_TOKEN)
2019
private readonly pullRequestService: IPullRequestsService,
21-
) { }
2220

23-
async execute(organizationAndTeamData: OrganizationAndTeamData) {
24-
const period = this.calculatePeriod();
21+
private readonly logger: PinoLoggerService,
22+
) {}
2523

26-
const mergedPullRequests =
27-
await this.codeManagementService.getPullRequestsWithFiles({
28-
organizationAndTeamData: organizationAndTeamData,
29-
filters: {
30-
period,
31-
prStatus: 'merged',
32-
},
33-
});
24+
async execute(
25+
organizationAndTeamData: OrganizationAndTeamData,
26+
automationExecutionsPRs: number[],
27+
) {
28+
if (!automationExecutionsPRs?.length) {
29+
return [];
30+
}
3431

35-
const closedPullRequests =
36-
await this.codeManagementService.getPullRequestsWithFiles({
37-
organizationAndTeamData: organizationAndTeamData,
38-
filters: {
39-
period,
40-
prStatus: 'closed',
41-
},
42-
});
32+
const pullRequests =
33+
await this.pullRequestService.findPullRequestsWithDeliveredSuggestions(
34+
organizationAndTeamData.organizationId,
35+
automationExecutionsPRs,
36+
[PullRequestState.MERGED, PullRequestState.CLOSED],
37+
);
4338

44-
const allPullRequests = [...mergedPullRequests, ...closedPullRequests];
39+
if (!pullRequests?.length) {
40+
return [];
41+
}
4542

46-
return await this.getReactions(
47-
allPullRequests,
48-
organizationAndTeamData,
49-
);
43+
return await this.getReactions(pullRequests, organizationAndTeamData);
5044
}
5145

5246
private async getReactions(
53-
pullRequests: PullRequestWithFiles[],
47+
pullRequests: IPullRequestWithDeliveredSuggestions[],
5448
organizationAndTeamData: OrganizationAndTeamData,
55-
): Promise<ICodeReviewFeedback[]> {
56-
const reactions: any[] = [];
49+
) {
50+
const reactionsPromises = pullRequests.map(async (pr) => {
51+
if (!pr.suggestions?.length) {
52+
return [];
53+
}
5754

58-
if (!pullRequests?.length) {
59-
return [];
60-
}
55+
const suggestionsByCommentId = new Map(
56+
pr.suggestions.map((s) => [s.comment?.id, s]),
57+
);
6158

62-
for (const pr of pullRequests) {
6359
const comments =
6460
await this.codeManagementService.getPullRequestReviewComment({
65-
organizationAndTeamData: organizationAndTeamData,
61+
organizationAndTeamData,
6662
filters: {
67-
repository: pr.repository,
68-
pullRequestNumber: pr.pull_number,
63+
repository: pr.repository.name,
64+
pullRequestNumber: pr.number,
6965
},
7066
});
7167

72-
const suggestions =
73-
await this.pullRequestService.findSuggestionsByPR(
74-
organizationAndTeamData.organizationId,
75-
pr.pull_number,
76-
DeliveryStatus.SENT,
77-
);
68+
const commentsLinkedToSuggestions = comments.filter((comment) => {
69+
const commentId = comment?.threadId
70+
? comment.threadId
71+
: comment?.notes?.[0]?.id || comment?.id;
72+
return suggestionsByCommentId.has(commentId);
73+
});
7874

79-
if (!suggestions?.length) {
80-
continue;
75+
if (!commentsLinkedToSuggestions.length) {
76+
return [];
8177
}
8278

83-
const commentsLinkedToSuggestions = comments.filter((comment) =>
84-
suggestions?.some(
85-
(suggestion) => {
86-
// We dont save the commentId for azure, but the threadId.
87-
if (comment?.threadId) {
88-
return suggestion?.comment?.id === comment?.threadId;
89-
}
90-
else {
91-
return suggestion?.comment?.id === (comment?.notes?.[0]?.id || comment?.id);
92-
}
93-
}
94-
),
95-
);
96-
9779
const reactionsInComments =
9880
await this.codeManagementService.countReactions({
99-
organizationAndTeamData: organizationAndTeamData,
81+
organizationAndTeamData,
10082
comments: commentsLinkedToSuggestions,
101-
pr,
83+
pr: {
84+
pull_number: pr.number,
85+
repository: pr.repository.name,
86+
},
10287
});
10388

104-
// Adds the suggestionId to each reaction
105-
const reactionsWithSuggestionId = reactionsInComments
106-
.filter((reaction) =>
107-
suggestions.some(
108-
(s) => s?.comment?.id === reaction.comment.id,
109-
),
110-
)
89+
if (!reactionsInComments?.length) {
90+
return [];
91+
}
92+
93+
return reactionsInComments
11194
.map((reaction) => {
112-
const suggestion = suggestions.find(
113-
(s) => s?.comment?.id === reaction.comment.id,
95+
const suggestion = suggestionsByCommentId.get(
96+
reaction.comment.id,
11497
);
98+
if (!suggestion) {
99+
return null;
100+
}
101+
115102
return {
116103
reactions: reaction.reactions,
117104
comment: {
118105
id: reaction.comment.id,
119106
pullRequestReviewId:
120107
reaction.comment?.pull_request_review_id,
121108
},
122-
suggestionId: suggestion?.id,
109+
suggestionId: suggestion.id,
123110
pullRequest: {
124111
id: reaction.pullRequest.id,
125112
number: reaction.pullRequest.number,
126113
repository: {
127114
id:
128115
reaction?.pullRequest?.repository?.id ||
129-
pr?.repositoryData?.id,
116+
pr.repository.id,
130117
fullName:
131-
pr?.repositoryData?.fullName ||
132-
reaction?.pullRequest?.repository?.fullName,
118+
reaction?.pullRequest?.repository
119+
?.fullName || pr.repository.name,
133120
},
134121
},
135122
organizationId: organizationAndTeamData.organizationId,
136123
};
137-
});
138-
139-
reactions.push(...reactionsWithSuggestionId);
124+
})
125+
.filter((reaction) => reaction !== null);
126+
});
127+
128+
const reactionsResults = await Promise.all(reactionsPromises);
129+
const flattenedReactions = reactionsResults.flat();
130+
131+
const prsWithoutReactions = pullRequests.filter((pr, index) => {
132+
return reactionsResults[index].length === 0;
133+
});
134+
135+
if (prsWithoutReactions.length > 0) {
136+
this.logger.log({
137+
message: 'PRs without reactions summary',
138+
context: GetReactionsUseCase.name,
139+
metadata: {
140+
organizationId: organizationAndTeamData.organizationId,
141+
totalPRs: pullRequests.length,
142+
prsWithReactions:
143+
pullRequests.length - prsWithoutReactions.length,
144+
prsWithoutReactions: prsWithoutReactions.length,
145+
prsWithoutReactionsDetails: prsWithoutReactions.map(
146+
(pr) => ({
147+
prNumber: pr.number,
148+
repository: pr.repository.name,
149+
suggestionsCount: pr.suggestions?.length || 0,
150+
}),
151+
),
152+
},
153+
});
140154
}
141155

142-
return reactions;
143-
}
144-
145-
private calculatePeriod() {
146-
const now = new Date();
147-
const endDate = now;
148-
const startDate = new Date(now);
149-
150-
startDate.setDate(startDate.getDate() - 1);
151-
startDate.setHours(0, 0, 0, 0);
152-
153-
return {
154-
startDate: moment(startDate).format('YYYY-MM-DD HH:mm'),
155-
endDate: moment(endDate).format('YYYY-MM-DD HH:mm'),
156-
};
156+
return flattenedReactions;
157157
}
158158
}

src/core/application/use-cases/codeReviewFeedback/save-feedback.use-case.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,19 @@ export class SaveCodeReviewFeedbackUseCase implements IUseCase {
1717
private readonly logger: PinoLoggerService,
1818
) {}
1919

20-
async execute(
21-
organizationAndTeamData: OrganizationAndTeamData,
22-
): Promise<CodeReviewFeedbackEntity[]> {
20+
async execute(payload: {
21+
organizationId: string;
22+
teamId: string;
23+
automationExecutionsPRs: number[];
24+
}): Promise<CodeReviewFeedbackEntity[]> {
2325
try {
24-
const reactions = await this.getReactions(organizationAndTeamData);
26+
const reactions = await this.getReactions(
27+
{
28+
organizationId: payload.organizationId,
29+
teamId: payload.teamId,
30+
},
31+
payload.automationExecutionsPRs,
32+
);
2533

2634
return await this.codeReviewFeedbackService.bulkCreate(
2735
reactions as Omit<ICodeReviewFeedback, 'uuid'>[],
@@ -31,15 +39,19 @@ export class SaveCodeReviewFeedbackUseCase implements IUseCase {
3139
message: 'Error save code review feedback',
3240
context: SaveCodeReviewFeedbackUseCase.name,
3341
error,
34-
metadata: { organizationAndTeamData },
42+
metadata: { payload },
3543
});
3644
throw error;
3745
}
3846
}
3947

4048
private async getReactions(
4149
organizationAndTeamData: OrganizationAndTeamData,
50+
automationExecutionsPRs: number[],
4251
) {
43-
return this.getReactionsUseCase.execute(organizationAndTeamData);
52+
return this.getReactionsUseCase.execute(
53+
organizationAndTeamData,
54+
automationExecutionsPRs,
55+
);
4456
}
4557
}

src/core/domain/pullRequests/contracts/pullRequests.repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export interface IPullRequestsRepository {
5959
findPullRequestsWithDeliveredSuggestions(
6060
organizationId: string,
6161
prNumbers: number[],
62-
status: string,
62+
status: string | string[],
6363
): Promise<IPullRequestWithDeliveredSuggestions[]>;
6464
findByOrganizationAndRepositoryWithStatusAndSyncedFlag(
6565
organizationId: string,

src/core/infrastructure/adapters/repositories/mongoose/pullRequests.repository.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,16 +235,20 @@ export class PullRequestsRepository implements IPullRequestsRepository {
235235
async findPullRequestsWithDeliveredSuggestions(
236236
organizationId: string,
237237
prNumbers: number[],
238-
status: string,
238+
status: string | string[],
239239
): Promise<IPullRequestWithDeliveredSuggestions[]> {
240240
try {
241+
const statusFilter = Array.isArray(status)
242+
? { $in: status }
243+
: status;
244+
241245
const result = await this.pullRequestsModel
242246
.aggregate([
243247
{
244248
$match: {
245249
organizationId: organizationId,
246250
number: { $in: prNumbers },
247-
status: status,
251+
status: statusFilter,
248252
},
249253
},
250254
{

0 commit comments

Comments
 (0)