Skip to content

Commit 2a76eac

Browse files
authored
feat : csm pagination view
feat : csm pagination view
2 parents e2c0a19 + a83fbcc commit 2a76eac

25 files changed

Lines changed: 276 additions & 182 deletions

File tree

packages/analysis-engine/src/csm.spec.ts

Lines changed: 13 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -438,13 +438,8 @@ describe("csm", () => {
438438

439439
describe("buildPaginatedCSMDict", () => {
440440
it("should load first page when lastCommitId is not provided", () => {
441-
const perPage = 2;
442-
const result = buildPaginatedCSMDict(
443-
fakeCommitNodeDict,
444-
fakeStemDict,
445-
"master",
446-
perPage
447-
);
441+
const commitCountPerPage = 2;
442+
const result = buildPaginatedCSMDict(fakeCommitNodeDict, fakeStemDict, "master", commitCountPerPage);
448443

449444
expect(result).toBeDefined();
450445
expect(result.master).toBeDefined();
@@ -456,12 +451,12 @@ describe("csm", () => {
456451
});
457452

458453
it("should load next page when lastCommitId is provided", () => {
459-
const perPage = 2;
454+
const commitCountPerPage = 2;
460455
const result = buildPaginatedCSMDict(
461456
fakeCommitNodeDict,
462457
fakeStemDict,
463458
"master",
464-
perPage,
459+
commitCountPerPage,
465460
"4" // Last commit of first page
466461
);
467462

@@ -474,12 +469,12 @@ describe("csm", () => {
474469
});
475470

476471
it("should return remaining nodes when perPage exceeds remaining nodes", () => {
477-
const perPage = 10;
472+
const commitCountPerPage = 10;
478473
const result = buildPaginatedCSMDict(
479474
fakeCommitNodeDict,
480475
fakeStemDict,
481476
"master",
482-
perPage,
477+
commitCountPerPage,
483478
"2" // Only [1, 0] remaining
484479
);
485480

@@ -491,12 +486,12 @@ describe("csm", () => {
491486
});
492487

493488
it("should return empty array when no more nodes available", () => {
494-
const perPage = 2;
489+
const commitCountPerPage = 2;
495490
const result = buildPaginatedCSMDict(
496491
fakeCommitNodeDict,
497492
fakeStemDict,
498493
"master",
499-
perPage,
494+
commitCountPerPage,
500495
"0" // Last node
501496
);
502497

@@ -507,44 +502,23 @@ describe("csm", () => {
507502

508503
it("should throw error when lastCommitId is invalid", () => {
509504
expect(() => {
510-
buildPaginatedCSMDict(
511-
fakeCommitNodeDict,
512-
fakeStemDict,
513-
"master",
514-
2,
515-
"invalid-commit-id"
516-
);
505+
buildPaginatedCSMDict(fakeCommitNodeDict, fakeStemDict, "master", 2, "invalid-commit-id");
517506
}).toThrow("Invalid lastCommitId");
518507
});
519508

520509
it("should throw error when perPage is less than or equal to 0", () => {
521510
expect(() => {
522-
buildPaginatedCSMDict(
523-
fakeCommitNodeDict,
524-
fakeStemDict,
525-
"master",
526-
0
527-
);
511+
buildPaginatedCSMDict(fakeCommitNodeDict, fakeStemDict, "master", 0);
528512
}).toThrow("perPage must be greater than 0");
529513

530514
expect(() => {
531-
buildPaginatedCSMDict(
532-
fakeCommitNodeDict,
533-
fakeStemDict,
534-
"master",
535-
-1
536-
);
515+
buildPaginatedCSMDict(fakeCommitNodeDict, fakeStemDict, "master", -1);
537516
}).toThrow("perPage must be greater than 0");
538517
});
539518

540519
it("should throw error when base branch does not exist", () => {
541520
expect(() => {
542-
buildPaginatedCSMDict(
543-
fakeCommitNodeDict,
544-
fakeStemDict,
545-
"non-existent-branch",
546-
2
547-
);
521+
buildPaginatedCSMDict(fakeCommitNodeDict, fakeStemDict, "non-existent-branch", 2);
548522
}).toThrow("no master-stem");
549523
});
550524

@@ -566,14 +540,7 @@ describe("csm", () => {
566540
},
567541
} as unknown as PullRequest;
568542

569-
const result = buildPaginatedCSMDict(
570-
fakeCommitNodeDict,
571-
fakeStemDict,
572-
"master",
573-
1,
574-
undefined,
575-
[fakePR]
576-
);
543+
const result = buildPaginatedCSMDict(fakeCommitNodeDict, fakeStemDict, "master", 1, undefined, [fakePR]);
577544

578545
expect(result.master[0].base.commit.id).toBe("5");
579546
// PR integration logic should be applied

packages/analysis-engine/src/csm.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,13 @@ export const buildPaginatedCSMDict = (
165165
commitDict: CommitDict,
166166
stemDict: StemDict,
167167
baseBranchName: string,
168-
perPage: number,
168+
commitCountPerPage: number,
169169
lastCommitId?: string,
170170
pullRequests: Array<PullRequest> = []
171171
): CSMDictionary => {
172-
// Validate perPage
173-
if (perPage <= 0) {
174-
throw new Error("perPage must be greater than 0");
172+
// Validate commitCountPerPage
173+
if (commitCountPerPage <= 0) {
174+
throw new Error("commitCountPerPage must be greater than 0");
175175
}
176176

177177
// Validate stemDict
@@ -196,7 +196,7 @@ export const buildPaginatedCSMDict = (
196196
}
197197

198198
// Calculate end index and extract page nodes
199-
const endIndex = Math.min(startIndex + perPage, baseStem.nodes.length);
199+
const endIndex = Math.min(startIndex + commitCountPerPage, baseStem.nodes.length);
200200
const pageNodes = baseStem.nodes.slice(startIndex, endIndex);
201201

202202
// Build CSM nodes with PR integration

packages/analysis-engine/src/index.ts

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import getCommitRaws from "./parser";
88
import { PluginOctokit } from "./pluginOctokit";
99
import { buildStemDict } from "./stem";
1010
import { getSummary } from "./summary";
11-
import type { CSMNode } from "./types";
11+
import type { AnalyzeGitResult } from "./types";
1212

1313
export { buildPaginatedCSMDict } from "./csm";
1414

@@ -30,6 +30,12 @@ export class AnalysisEngine {
3030

3131
private baseBranchName!: string;
3232

33+
// Cached data
34+
private commitDict?: ReturnType<typeof buildCommitDict>;
35+
private pullRequests?: Awaited<ReturnType<PluginOctokit["getPullRequests"]>>;
36+
private stemDict?: ReturnType<typeof buildStemDict>;
37+
private isPRSuccess: boolean = true;
38+
3339
constructor(args: AnalysisEngineArgs) {
3440
this.insertArgs(args);
3541
}
@@ -51,76 +57,88 @@ export class AnalysisEngine {
5157
this.octokit = container.resolve(PluginOctokit);
5258
};
5359

54-
public analyzeGit = async (perPage?: number, lastCommitId?: string) => {
55-
let isPRSuccess = true;
60+
public init = async () => {
5661
if (this.isDebugMode) console.log("baseBranchName: ", this.baseBranchName);
5762

5863
const commitRaws = getCommitRaws(this.gitLog);
59-
if (this.isDebugMode) {
60-
console.log("commitRaws: ", commitRaws);
61-
}
64+
if (this.isDebugMode) console.log("commitRaws: ", commitRaws);
6265

63-
const commitDict = buildCommitDict(commitRaws);
64-
if (this.isDebugMode) console.log("commitDict: ", commitDict);
66+
this.commitDict = buildCommitDict(commitRaws);
67+
if (this.isDebugMode) console.log("commitDict: ", this.commitDict);
6568

66-
const pullRequests = await this.octokit
69+
this.pullRequests = await this.octokit
6770
.getPullRequests()
6871
.catch((err) => {
6972
console.error(err);
70-
isPRSuccess = false;
73+
this.isPRSuccess = false;
7174
return [];
7275
})
7376
.then((pullRequests) => {
7477
console.log("success, pr = ", pullRequests);
7578
return pullRequests;
7679
});
77-
if (this.isDebugMode) console.log("pullRequests: ", pullRequests);
80+
if (this.isDebugMode) console.log("pullRequests: ", this.pullRequests);
7881

79-
const stemDict = buildStemDict(commitDict, this.baseBranchName);
80-
if (this.isDebugMode) console.log("stemDict: ", stemDict);
82+
this.stemDict = buildStemDict(this.commitDict, this.baseBranchName);
83+
if (this.isDebugMode) console.log("stemDict: ", this.stemDict);
84+
};
85+
86+
public analyzeGit = async (commitCountPerPage?: number, lastCommitId?: string): Promise<AnalyzeGitResult> => {
87+
if (!this.commitDict || !this.stemDict || !this.pullRequests) {
88+
throw new Error("AnalysisEngine not initialized. Call init() first.");
89+
}
8190

8291
// Paginated CSM
83-
if (perPage) {
92+
if (commitCountPerPage) {
8493
const csmDict = buildPaginatedCSMDict(
85-
commitDict,
86-
stemDict,
94+
this.commitDict,
95+
this.stemDict,
8796
this.baseBranchName,
88-
perPage,
97+
commitCountPerPage,
8998
lastCommitId,
90-
pullRequests
99+
this.pullRequests
91100
);
92101
const list = csmDict[this.baseBranchName] ?? [];
93-
const lastNode: CSMNode | undefined = list.length > 0 ? list[list.length - 1] : undefined;
102+
const lastNode = list.length > 0 ? list[list.length - 1] : undefined;
94103

95-
const isLastPage = list.length < perPage;
96-
const nextCommitId = !isLastPage && lastNode ? lastNode.base.commit.id : null;
104+
const isLastPage = list.length < commitCountPerPage;
105+
const nextCommitId = !isLastPage && lastNode ? lastNode.base.commit.id : undefined;
97106

98107
return {
99-
isPRSuccess,
108+
isPRSuccess: this.isPRSuccess,
100109
csmDict,
101110
nextCommitId,
102111
isLastPage,
103112
};
104113
} else {
105114
// Non-paginated CSM
106-
const csmDict = buildCSMDict(commitDict, stemDict, this.baseBranchName, pullRequests);
115+
const csmDict = buildCSMDict(this.commitDict, this.stemDict, this.baseBranchName, this.pullRequests);
107116
if (this.isDebugMode) console.log("csmDict: ", csmDict);
108-
const nodes = stemDict.get(this.baseBranchName)?.nodes?.map(({ commit }) => commit);
117+
const nodes = this.stemDict.get(this.baseBranchName)?.nodes?.map(({ commit }) => commit);
109118
const geminiCommitSummary = await getSummary(nodes ? nodes?.slice(-10) : []);
110119
if (this.isDebugMode) console.log("GeminiCommitSummary: ", geminiCommitSummary);
111120

112121
return {
113-
isPRSuccess,
122+
isPRSuccess: this.isPRSuccess,
114123
csmDict,
115-
nextCommitId: null,
124+
nextCommitId: undefined,
116125
isLastPage: true,
117126
};
118127
}
119128
};
120129

130+
public getBaseBranchName = () => {
131+
return this.baseBranchName;
132+
};
133+
121134
public updateArgs = (args: AnalysisEngineArgs) => {
122135
if (container.isRegistered("OctokitOptions")) container.clearInstances();
123136
this.insertArgs(args);
137+
// Clear cached data
138+
this.commitDict = undefined;
139+
this.stemDict = undefined;
140+
this.pullRequests = undefined;
141+
this.isPRSuccess = true;
124142
};
125143
}
126144

packages/analysis-engine/src/types/CSM.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,9 @@ export interface CSMNode {
88
export interface CSMDictionary {
99
[branch: string]: CSMNode[];
1010
}
11+
export type AnalyzeGitResult = {
12+
isPRSuccess: boolean;
13+
csmDict: CSMDictionary;
14+
nextCommitId: string | undefined;
15+
isLastPage: boolean;
16+
};

packages/view/src/App.tsx

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,17 @@ import { RefreshButton } from "components/RefreshButton";
1212
import type { IDESentEvents } from "types/IDESentEvents";
1313
import { useBranchStore, useDataStore, useGithubInfo, useLoadingStore, useThemeStore } from "store";
1414
import { THEME_INFO } from "components/ThemeSelector/ThemeSelector.const";
15+
import { COMMIT_COUNT_PER_PAGE } from "constants/constants";
1516

1617
const App = () => {
1718
const initRef = useRef<boolean>(false);
1819
const { handleChangeAnalyzedData } = useAnalayzedData();
19-
const filteredData = useDataStore((state) => state.filteredData);
20+
const { filteredData, nextCommitId, isLastPage } = useDataStore((state) => ({
21+
filteredData: state.filteredData,
22+
nextCommitId: state.nextCommitId,
23+
isLastPage: state.isLastPage,
24+
}));
25+
2026
const { handleChangeBranchList } = useBranchStore();
2127
const { handleGithubInfo } = useGithubInfo();
2228
const { loading, setLoading } = useLoadingStore();
@@ -26,19 +32,29 @@ const App = () => {
2632
useEffect(() => {
2733
if (initRef.current === false) {
2834
const callbacks: IDESentEvents = {
29-
handleChangeAnalyzedData,
35+
handleChangeAnalyzedData: (payload) => handleChangeAnalyzedData(payload),
3036
handleChangeBranchList,
3137
handleGithubInfo,
3238
};
3339
setLoading(true);
3440
ideAdapter.addIDESentEventListener(callbacks);
35-
ideAdapter.sendFetchAnalyzedDataMessage();
41+
ideAdapter.sendFetchAnalyzedDataMessage({ commitCountPerPage: COMMIT_COUNT_PER_PAGE });
3642
ideAdapter.sendFetchBranchListMessage();
3743
ideAdapter.sendFetchGithubInfo();
3844
initRef.current = true;
3945
}
4046
}, [handleChangeAnalyzedData, handleChangeBranchList, handleGithubInfo, ideAdapter, setLoading]);
4147

48+
const handleLoadMore = () => {
49+
if (loading || isLastPage) return;
50+
51+
setLoading(true);
52+
ideAdapter.sendFetchAnalyzedDataMessage({
53+
commitCountPerPage: COMMIT_COUNT_PER_PAGE,
54+
lastCommitId: nextCommitId,
55+
});
56+
};
57+
4258
if (loading) {
4359
return (
4460
<BounceLoader
@@ -66,10 +82,20 @@ const App = () => {
6682
</div>
6783
<div>
6884
{filteredData.length !== 0 ? (
69-
<div className="middle-container">
70-
<VerticalClusterList />
71-
<Statistics />
72-
</div>
85+
<>
86+
<div className="middle-container">
87+
<VerticalClusterList />
88+
<Statistics />
89+
</div>
90+
<div className="load-more-container">
91+
<button
92+
onClick={handleLoadMore}
93+
disabled={isLastPage || loading}
94+
>
95+
{loading ? "Loading..." : isLastPage ? "No More Commits" : "Load More"}
96+
</button>
97+
</div>
98+
</>
7399
) : (
74100
<div className="no-commits-container">
75101
<MonoLogo />

packages/view/src/components/@common/Author/Author.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Tooltip, Avatar } from "@mui/material";
22

33
import type { AuthorInfo } from "types";
44

5-
import { GITHUB_URL } from "../../../constants/constants";
5+
import { GITHUB_URL } from "constants/constants";
66

77
import { AVATAR_STYLE, TOOLTIP_STYLE } from "./Author.const";
88

0 commit comments

Comments
 (0)