Skip to content

Commit 00acb5a

Browse files
authored
feat: change script to take parameters from configuration file instead of CLI gf-595 (#595) (#596)
1 parent 1f0b90c commit 00acb5a

File tree

9 files changed

+117
-50
lines changed

9 files changed

+117
-50
lines changed

apps/frontend/src/pages/project/libs/components/setup-analytics-modal/setup-analytics-modal.tsx

+52-7
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,35 @@ const SetupAnalyticsModal = ({
4949

5050
const pm2StartupScript = "pm2 startup";
5151

52-
const analyticsScript = useMemo<string>(() => {
52+
const analyticsScriptConfiguration = useMemo<string>(() => {
5353
if (!hasProjectApiKey || !hasAuthenticatedUser) {
5454
return "";
5555
}
5656

5757
const apiKey = project.apiKey as string;
5858
const userId = String(authenticatedUser.id);
5959

60-
return `npx @git-fit/analytics@latest track ${apiKey} ${userId} <project-path-1> <project-path-2> ...`;
60+
return `{
61+
"apiKey": "${apiKey}",
62+
"userId": ${userId},
63+
"repoPaths": [
64+
65+
]
66+
}`;
6167
}, [hasProjectApiKey, hasAuthenticatedUser, project, authenticatedUser]);
6268

69+
const analyticsScript = useMemo<string>(() => {
70+
if (!hasProjectApiKey || !hasAuthenticatedUser) {
71+
return "";
72+
}
73+
74+
return "npx @git-fit/analytics@latest track <config-path>";
75+
}, [hasProjectApiKey, hasAuthenticatedUser]);
76+
6377
const { control, errors, handleSubmit, handleValueSet } = useAppForm({
6478
defaultValues: {
6579
analyticsScript,
80+
analyticsScriptConfiguration,
6681
apiKey: project.apiKey ?? "",
6782
pm2StartupScript,
6883
projectId: project.id,
@@ -96,6 +111,10 @@ const SetupAnalyticsModal = ({
96111
handleCopyApiKeyToClipboard(project.apiKey as string);
97112
}, [handleCopyApiKeyToClipboard, project]);
98113

114+
const handleCopyAnalyticsScriptConfigurationClick = useCallback(() => {
115+
handleCopyScriptToClipboard(analyticsScriptConfiguration);
116+
}, [handleCopyScriptToClipboard, analyticsScriptConfiguration]);
117+
99118
const handleCopyAnalyticsScriptClick = useCallback(() => {
100119
handleCopyScriptToClipboard(analyticsScript);
101120
}, [handleCopyScriptToClipboard, analyticsScript]);
@@ -221,21 +240,47 @@ const SetupAnalyticsModal = ({
221240
</li>
222241
<li className={styles["list-item"]}>
223242
<span className={styles["list-item-title"]}>
224-
Clone your project repository.
243+
Clone your project repositories.
225244
</span>
226245
<p className={styles["list-item-text"]}>
227-
Use Git to clone your project repository to your local machine.
246+
Use Git to clone your project repositories to your local
247+
machine.
228248
</p>
229249
</li>
230250

251+
<li className={styles["list-item"]}>
252+
<span className={styles["list-item-title"]}>
253+
Save the following configuration file to your local machine and
254+
add local paths to all of your repositories to it.
255+
</span>
256+
257+
<Input
258+
control={control}
259+
errors={errors}
260+
isLabelHidden
261+
isReadOnly
262+
label="Analytics script configuration"
263+
name="analyticsScriptConfiguration"
264+
placeholder="Need to generate API key"
265+
rightIcon={
266+
<IconButton
267+
iconName="clipboard"
268+
isDisabled={isCopyButtonDisabled}
269+
label="Copy script configuration"
270+
onClick={handleCopyAnalyticsScriptConfigurationClick}
271+
/>
272+
}
273+
rowsCount={9}
274+
/>
275+
</li>
276+
231277
<li className={styles["list-item"]}>
232278
<span className={styles["list-item-title"]}>
233279
Prepare the script.
234280
</span>
235281
<p className={styles["list-item-text"]}>
236-
Copy the command below and replace &lt;project-path-1&gt;,
237-
&lt;project-path-2&gt;, ... placeholder with your local
238-
repositories paths:
282+
Copy the command below and replace &lt;config-path&gt; with the
283+
path to your configuration file from the previous step.
239284
</p>
240285
<Input
241286
control={control}

scripts/analytics/src/libs/helpers/execute-command/execute-command.helper.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ const execAsync = promisify(exec);
55

66
const executeCommand = async (
77
command: string,
8+
cwd: string,
89
): Promise<{ stderr: Buffer | string; stdout: Buffer | string }> => {
9-
return await execAsync(command);
10+
return await execAsync(command, { cwd });
1011
};
1112

1213
export { executeCommand };

scripts/analytics/src/libs/modules/analytics-cli/base-analytics-cli.ts

+25-8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import { EMPTY_LENGTH } from "@git-fit/shared";
12
import { Command } from "commander";
3+
import fs from "node:fs/promises";
24
import path from "node:path";
35
import pm2 from "pm2";
46

57
import { executeCommand } from "~/libs/helpers/helpers.js";
68
import { type Logger } from "~/libs/modules/logger/logger.js";
7-
import { EMPTY_LENGTH } from "~/modules/analytics/libs/constants/constants.js";
9+
import { type AnalyticsScriptConfig } from "~/libs/types/types.js";
810
import { type AuthAnalyticsService } from "~/modules/auth-analytics/auth-analytics.js";
911

1012
type Constructor = {
@@ -27,8 +29,10 @@ class BaseAnalyticsCli {
2729

2830
private async setupAutoStart(): Promise<void> {
2931
try {
30-
const { stderr: saveError, stdout: saveOut } =
31-
await executeCommand("pm2 save");
32+
const { stderr: saveError, stdout: saveOut } = await executeCommand(
33+
"pm2 save",
34+
process.cwd(),
35+
);
3236

3337
if (saveError) {
3438
this.logger.error(`PM2 save error: ${saveError as string}`);
@@ -47,11 +51,22 @@ class BaseAnalyticsCli {
4751

4852
private setupCommands(): void {
4953
this.program
50-
.command("track <apiKey> <userId> <repoPaths...>")
54+
.command("track <configPath>")
5155
.description("Start the background job for collecting statistics")
52-
.action(async (apiKey: string, userId: string, repoPaths: string[]) => {
56+
.action(async (configPath: string) => {
57+
if (!configPath) {
58+
this.logger.error("Configuration path is not provided.");
59+
60+
return;
61+
}
62+
63+
const config = JSON.parse(
64+
await fs.readFile(configPath, "utf8"),
65+
) as AnalyticsScriptConfig;
66+
const { apiKey, repoPaths, userId } = config;
67+
5368
if (!apiKey || !userId || repoPaths.length === EMPTY_LENGTH) {
54-
this.logger.error("Not all command arguments are provided.");
69+
this.logger.error("Configuration is not full.");
5570

5671
return;
5772
}
@@ -62,6 +77,8 @@ class BaseAnalyticsCli {
6277
);
6378

6479
if (!project) {
80+
this.logger.error("API key is not valid.");
81+
6582
return;
6683
}
6784

@@ -81,10 +98,10 @@ class BaseAnalyticsCli {
8198

8299
pm2.start(
83100
{
84-
args: [apiKey, userId, ...repoPaths],
101+
args: [configPath],
85102
autorestart: false,
86103
error: `${project.projectName}-err.log`,
87-
name: project.projectName,
104+
name: `GitFit - ${project.projectName}`,
88105
output: `${project.projectName}-out.log`,
89106
script: scriptPath,
90107
},

scripts/analytics/src/libs/modules/analytics-cli/init-analytics-cli.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,12 @@ import {
1010
CRON_SCHEDULE,
1111
} from "./libs/constants/constants.js";
1212

13-
const [apiKey, userId, ...repoPaths] = process.argv.slice(
14-
ARGUMENT_START_INDEX,
15-
) as [string, string, string];
13+
const [configPath] = process.argv.slice(ARGUMENT_START_INDEX) as [string];
1614

1715
const analyticsService = new AnalyticsService({
1816
analyticsApi,
19-
apiKey,
17+
configPath,
2018
gitService,
21-
repoPaths,
22-
userId,
2319
});
2420

2521
taskScheduler.start(

scripts/analytics/src/libs/modules/git-service/base-git-service.module.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { type GITService } from "./libs/types/types.js";
22

33
class BaseGITService implements GITService {
4-
public getFetchCommand = (repoPath: string): string => {
5-
return `git -C ${repoPath} fetch`;
4+
public getFetchCommand = (): string => {
5+
return "git fetch";
66
};
77

8-
public getShortLogCommand = (repoPath: string, since: string): string => {
9-
return `git -C ${repoPath} shortlog -sne --all --no-merges --since="${since}"`;
8+
public getShortLogCommand = (since: string): string => {
9+
return `git shortlog -sne --all --no-merges --since="${since}"`;
1010
};
1111
}
1212

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
type GITService = {
2-
getFetchCommand: (repoPath: string) => string;
3-
getShortLogCommand: (repoPath: string, since: string) => string;
2+
getFetchCommand: () => string;
3+
getShortLogCommand: (since: string) => string;
44
};
55

66
export { type GITService };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type AnalyticsScriptConfig = {
2+
apiKey: string;
3+
repoPaths: string[];
4+
userId: number;
5+
};
6+
7+
export { type AnalyticsScriptConfig };

scripts/analytics/src/libs/types/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export { type AnalyticsScriptConfig } from "./analytics-script-config.js";
12
export {
23
type ServerErrorDetail,
34
type ServerErrorResponse,

scripts/analytics/src/modules/analytics/analytics.service.ts

+22-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import fs from "node:fs/promises";
2+
13
import { executeCommand, formatDate } from "~/libs/helpers/helpers.js";
24
import { type GITService } from "~/libs/modules/git-service/git-service.js";
35
import { logger } from "~/libs/modules/logger/logger.js";
6+
import { type AnalyticsScriptConfig } from "~/libs/types/types.js";
47

58
import { type analyticsApi } from "./analytics.js";
69
import {
@@ -16,39 +19,28 @@ import {
1619

1720
type Constructor = {
1821
analyticsApi: typeof analyticsApi;
19-
apiKey: string;
22+
configPath: string;
2023
gitService: GITService;
21-
repoPaths: string[];
22-
userId: string;
2324
};
2425

2526
class AnalyticsService {
2627
private analyticsApi: typeof analyticsApi;
27-
private apiKey: string;
28+
private configPath: string;
2829
private gitService: GITService;
29-
private repoPaths: string[];
30-
private userId: string;
31-
32-
public constructor({
33-
analyticsApi,
34-
apiKey,
35-
gitService,
36-
repoPaths,
37-
userId,
38-
}: Constructor) {
30+
31+
public constructor({ analyticsApi, configPath, gitService }: Constructor) {
3932
this.analyticsApi = analyticsApi;
40-
this.apiKey = apiKey;
33+
this.configPath = configPath;
4134
this.gitService = gitService;
42-
this.repoPaths = repoPaths;
43-
this.userId = userId;
4435
}
4536

4637
private async collectStatsByRepository(
4738
repoPath: string,
4839
): Promise<ActivityLogCreateItemRequestDto[]> {
4940
const stats: ActivityLogCreateItemRequestDto[] = [];
5041
const shortLogResult = await executeCommand(
51-
this.gitService.getShortLogCommand(repoPath, "midnight"),
42+
this.gitService.getShortLogCommand("midnight"),
43+
repoPath,
5244
);
5345

5446
const commitItems: CommitStatistics[] = [];
@@ -79,15 +71,23 @@ class AnalyticsService {
7971
}
8072

8173
private async fetchRepository(repoPath: string): Promise<void> {
82-
await executeCommand(this.gitService.getFetchCommand(repoPath));
74+
await executeCommand(this.gitService.getFetchCommand(), repoPath);
8375
logger.info(`Fetched latest updates for repo at path: ${repoPath}`);
8476
}
8577

78+
private async getConfig(): Promise<AnalyticsScriptConfig> {
79+
return JSON.parse(
80+
await fs.readFile(this.configPath, "utf8"),
81+
) as AnalyticsScriptConfig;
82+
}
83+
8684
public async collectAndSendStats(): Promise<void> {
8785
try {
86+
const config = await this.getConfig();
87+
const { apiKey, repoPaths, userId } = config;
8888
const statsAll = [];
8989

90-
for (const repoPath of this.repoPaths) {
90+
for (const repoPath of repoPaths) {
9191
await this.fetchRepository(repoPath);
9292
statsAll.push(...(await this.collectStatsByRepository(repoPath)));
9393
}
@@ -103,9 +103,9 @@ class AnalyticsService {
103103
return;
104104
}
105105

106-
await this.analyticsApi.sendAnalytics(this.apiKey, {
106+
await this.analyticsApi.sendAnalytics(apiKey, {
107107
items: stats,
108-
userId: Number(this.userId),
108+
userId: Number(userId),
109109
});
110110

111111
logger.info("Statistics successfully sent.");

0 commit comments

Comments
 (0)