diff --git a/apps/frontend/src/pages/project/libs/components/setup-analytics-modal/setup-analytics-modal.tsx b/apps/frontend/src/pages/project/libs/components/setup-analytics-modal/setup-analytics-modal.tsx index 8eed105da..554d3deff 100644 --- a/apps/frontend/src/pages/project/libs/components/setup-analytics-modal/setup-analytics-modal.tsx +++ b/apps/frontend/src/pages/project/libs/components/setup-analytics-modal/setup-analytics-modal.tsx @@ -49,7 +49,7 @@ const SetupAnalyticsModal = ({ const pm2StartupScript = "pm2 startup"; - const analyticsScript = useMemo(() => { + const analyticsScriptConfiguration = useMemo(() => { if (!hasProjectApiKey || !hasAuthenticatedUser) { return ""; } @@ -57,12 +57,27 @@ const SetupAnalyticsModal = ({ const apiKey = project.apiKey as string; const userId = String(authenticatedUser.id); - return `npx @git-fit/analytics@latest track ${apiKey} ${userId} ...`; + return `{ + "apiKey": "${apiKey}", + "userId": ${userId}, + "repoPaths": [ + + ] +}`; }, [hasProjectApiKey, hasAuthenticatedUser, project, authenticatedUser]); + const analyticsScript = useMemo(() => { + if (!hasProjectApiKey || !hasAuthenticatedUser) { + return ""; + } + + return "npx @git-fit/analytics@latest track "; + }, [hasProjectApiKey, hasAuthenticatedUser]); + const { control, errors, handleSubmit, handleValueSet } = useAppForm({ defaultValues: { analyticsScript, + analyticsScriptConfiguration, apiKey: project.apiKey ?? "", pm2StartupScript, projectId: project.id, @@ -96,6 +111,10 @@ const SetupAnalyticsModal = ({ handleCopyApiKeyToClipboard(project.apiKey as string); }, [handleCopyApiKeyToClipboard, project]); + const handleCopyAnalyticsScriptConfigurationClick = useCallback(() => { + handleCopyScriptToClipboard(analyticsScriptConfiguration); + }, [handleCopyScriptToClipboard, analyticsScriptConfiguration]); + const handleCopyAnalyticsScriptClick = useCallback(() => { handleCopyScriptToClipboard(analyticsScript); }, [handleCopyScriptToClipboard, analyticsScript]); @@ -221,21 +240,47 @@ const SetupAnalyticsModal = ({
  • - Clone your project repository. + Clone your project repositories.

    - Use Git to clone your project repository to your local machine. + Use Git to clone your project repositories to your local + machine.

  • +
  • + + Save the following configuration file to your local machine and + add local paths to all of your repositories to it. + + + + } + rowsCount={9} + /> +
  • +
  • Prepare the script.

    - Copy the command below and replace <project-path-1>, - <project-path-2>, ... placeholder with your local - repositories paths: + Copy the command below and replace <config-path> with the + path to your configuration file from the previous step.

    => { - return await execAsync(command); + return await execAsync(command, { cwd }); }; export { executeCommand }; diff --git a/scripts/analytics/src/libs/modules/analytics-cli/base-analytics-cli.ts b/scripts/analytics/src/libs/modules/analytics-cli/base-analytics-cli.ts index d2fbeee64..3886fd549 100644 --- a/scripts/analytics/src/libs/modules/analytics-cli/base-analytics-cli.ts +++ b/scripts/analytics/src/libs/modules/analytics-cli/base-analytics-cli.ts @@ -1,10 +1,12 @@ +import { EMPTY_LENGTH } from "@git-fit/shared"; import { Command } from "commander"; +import fs from "node:fs/promises"; import path from "node:path"; import pm2 from "pm2"; import { executeCommand } from "~/libs/helpers/helpers.js"; import { type Logger } from "~/libs/modules/logger/logger.js"; -import { EMPTY_LENGTH } from "~/modules/analytics/libs/constants/constants.js"; +import { type AnalyticsScriptConfig } from "~/libs/types/types.js"; import { type AuthAnalyticsService } from "~/modules/auth-analytics/auth-analytics.js"; type Constructor = { @@ -27,8 +29,10 @@ class BaseAnalyticsCli { private async setupAutoStart(): Promise { try { - const { stderr: saveError, stdout: saveOut } = - await executeCommand("pm2 save"); + const { stderr: saveError, stdout: saveOut } = await executeCommand( + "pm2 save", + process.cwd(), + ); if (saveError) { this.logger.error(`PM2 save error: ${saveError as string}`); @@ -47,11 +51,22 @@ class BaseAnalyticsCli { private setupCommands(): void { this.program - .command("track ") + .command("track ") .description("Start the background job for collecting statistics") - .action(async (apiKey: string, userId: string, repoPaths: string[]) => { + .action(async (configPath: string) => { + if (!configPath) { + this.logger.error("Configuration path is not provided."); + + return; + } + + const config = JSON.parse( + await fs.readFile(configPath, "utf8"), + ) as AnalyticsScriptConfig; + const { apiKey, repoPaths, userId } = config; + if (!apiKey || !userId || repoPaths.length === EMPTY_LENGTH) { - this.logger.error("Not all command arguments are provided."); + this.logger.error("Configuration is not full."); return; } @@ -62,6 +77,8 @@ class BaseAnalyticsCli { ); if (!project) { + this.logger.error("API key is not valid."); + return; } @@ -81,10 +98,10 @@ class BaseAnalyticsCli { pm2.start( { - args: [apiKey, userId, ...repoPaths], + args: [configPath], autorestart: false, error: `${project.projectName}-err.log`, - name: project.projectName, + name: `GitFit - ${project.projectName}`, output: `${project.projectName}-out.log`, script: scriptPath, }, diff --git a/scripts/analytics/src/libs/modules/analytics-cli/init-analytics-cli.ts b/scripts/analytics/src/libs/modules/analytics-cli/init-analytics-cli.ts index f685f3452..58e962f74 100644 --- a/scripts/analytics/src/libs/modules/analytics-cli/init-analytics-cli.ts +++ b/scripts/analytics/src/libs/modules/analytics-cli/init-analytics-cli.ts @@ -10,16 +10,12 @@ import { CRON_SCHEDULE, } from "./libs/constants/constants.js"; -const [apiKey, userId, ...repoPaths] = process.argv.slice( - ARGUMENT_START_INDEX, -) as [string, string, string]; +const [configPath] = process.argv.slice(ARGUMENT_START_INDEX) as [string]; const analyticsService = new AnalyticsService({ analyticsApi, - apiKey, + configPath, gitService, - repoPaths, - userId, }); taskScheduler.start( diff --git a/scripts/analytics/src/libs/modules/git-service/base-git-service.module.ts b/scripts/analytics/src/libs/modules/git-service/base-git-service.module.ts index e0bd65853..365922396 100644 --- a/scripts/analytics/src/libs/modules/git-service/base-git-service.module.ts +++ b/scripts/analytics/src/libs/modules/git-service/base-git-service.module.ts @@ -1,12 +1,12 @@ import { type GITService } from "./libs/types/types.js"; class BaseGITService implements GITService { - public getFetchCommand = (repoPath: string): string => { - return `git -C ${repoPath} fetch`; + public getFetchCommand = (): string => { + return "git fetch"; }; - public getShortLogCommand = (repoPath: string, since: string): string => { - return `git -C ${repoPath} shortlog -sne --all --no-merges --since="${since}"`; + public getShortLogCommand = (since: string): string => { + return `git shortlog -sne --all --no-merges --since="${since}"`; }; } diff --git a/scripts/analytics/src/libs/modules/git-service/libs/types/git-service.type.ts b/scripts/analytics/src/libs/modules/git-service/libs/types/git-service.type.ts index e1e53b930..9cfa209ab 100644 --- a/scripts/analytics/src/libs/modules/git-service/libs/types/git-service.type.ts +++ b/scripts/analytics/src/libs/modules/git-service/libs/types/git-service.type.ts @@ -1,6 +1,6 @@ type GITService = { - getFetchCommand: (repoPath: string) => string; - getShortLogCommand: (repoPath: string, since: string) => string; + getFetchCommand: () => string; + getShortLogCommand: (since: string) => string; }; export { type GITService }; diff --git a/scripts/analytics/src/libs/types/analytics-script-config.ts b/scripts/analytics/src/libs/types/analytics-script-config.ts new file mode 100644 index 000000000..a60127184 --- /dev/null +++ b/scripts/analytics/src/libs/types/analytics-script-config.ts @@ -0,0 +1,7 @@ +type AnalyticsScriptConfig = { + apiKey: string; + repoPaths: string[]; + userId: number; +}; + +export { type AnalyticsScriptConfig }; diff --git a/scripts/analytics/src/libs/types/types.ts b/scripts/analytics/src/libs/types/types.ts index 53dacc872..70da50798 100644 --- a/scripts/analytics/src/libs/types/types.ts +++ b/scripts/analytics/src/libs/types/types.ts @@ -1,3 +1,4 @@ +export { type AnalyticsScriptConfig } from "./analytics-script-config.js"; export { type ServerErrorDetail, type ServerErrorResponse, diff --git a/scripts/analytics/src/modules/analytics/analytics.service.ts b/scripts/analytics/src/modules/analytics/analytics.service.ts index 9cd3a3429..f55135fc0 100644 --- a/scripts/analytics/src/modules/analytics/analytics.service.ts +++ b/scripts/analytics/src/modules/analytics/analytics.service.ts @@ -1,6 +1,9 @@ +import fs from "node:fs/promises"; + import { executeCommand, formatDate } from "~/libs/helpers/helpers.js"; import { type GITService } from "~/libs/modules/git-service/git-service.js"; import { logger } from "~/libs/modules/logger/logger.js"; +import { type AnalyticsScriptConfig } from "~/libs/types/types.js"; import { type analyticsApi } from "./analytics.js"; import { @@ -16,31 +19,19 @@ import { type Constructor = { analyticsApi: typeof analyticsApi; - apiKey: string; + configPath: string; gitService: GITService; - repoPaths: string[]; - userId: string; }; class AnalyticsService { private analyticsApi: typeof analyticsApi; - private apiKey: string; + private configPath: string; private gitService: GITService; - private repoPaths: string[]; - private userId: string; - - public constructor({ - analyticsApi, - apiKey, - gitService, - repoPaths, - userId, - }: Constructor) { + + public constructor({ analyticsApi, configPath, gitService }: Constructor) { this.analyticsApi = analyticsApi; - this.apiKey = apiKey; + this.configPath = configPath; this.gitService = gitService; - this.repoPaths = repoPaths; - this.userId = userId; } private async collectStatsByRepository( @@ -48,7 +39,8 @@ class AnalyticsService { ): Promise { const stats: ActivityLogCreateItemRequestDto[] = []; const shortLogResult = await executeCommand( - this.gitService.getShortLogCommand(repoPath, "midnight"), + this.gitService.getShortLogCommand("midnight"), + repoPath, ); const commitItems: CommitStatistics[] = []; @@ -79,15 +71,23 @@ class AnalyticsService { } private async fetchRepository(repoPath: string): Promise { - await executeCommand(this.gitService.getFetchCommand(repoPath)); + await executeCommand(this.gitService.getFetchCommand(), repoPath); logger.info(`Fetched latest updates for repo at path: ${repoPath}`); } + private async getConfig(): Promise { + return JSON.parse( + await fs.readFile(this.configPath, "utf8"), + ) as AnalyticsScriptConfig; + } + public async collectAndSendStats(): Promise { try { + const config = await this.getConfig(); + const { apiKey, repoPaths, userId } = config; const statsAll = []; - for (const repoPath of this.repoPaths) { + for (const repoPath of repoPaths) { await this.fetchRepository(repoPath); statsAll.push(...(await this.collectStatsByRepository(repoPath))); } @@ -103,9 +103,9 @@ class AnalyticsService { return; } - await this.analyticsApi.sendAnalytics(this.apiKey, { + await this.analyticsApi.sendAnalytics(apiKey, { items: stats, - userId: Number(this.userId), + userId: Number(userId), }); logger.info("Statistics successfully sent.");