Skip to content

Commit

Permalink
feat: change script to take parameters from configuration file instea…
Browse files Browse the repository at this point in the history
…d of CLI gf-595 (#595) (#596)
  • Loading branch information
ArtemZag authored Oct 4, 2024
1 parent 1f0b90c commit 00acb5a
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,35 @@ const SetupAnalyticsModal = ({

const pm2StartupScript = "pm2 startup";

const analyticsScript = useMemo<string>(() => {
const analyticsScriptConfiguration = useMemo<string>(() => {
if (!hasProjectApiKey || !hasAuthenticatedUser) {
return "";
}

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

return `npx @git-fit/analytics@latest track ${apiKey} ${userId} <project-path-1> <project-path-2> ...`;
return `{
"apiKey": "${apiKey}",
"userId": ${userId},
"repoPaths": [
]
}`;
}, [hasProjectApiKey, hasAuthenticatedUser, project, authenticatedUser]);

const analyticsScript = useMemo<string>(() => {
if (!hasProjectApiKey || !hasAuthenticatedUser) {
return "";
}

return "npx @git-fit/analytics@latest track <config-path>";
}, [hasProjectApiKey, hasAuthenticatedUser]);

const { control, errors, handleSubmit, handleValueSet } = useAppForm({
defaultValues: {
analyticsScript,
analyticsScriptConfiguration,
apiKey: project.apiKey ?? "",
pm2StartupScript,
projectId: project.id,
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -221,21 +240,47 @@ const SetupAnalyticsModal = ({
</li>
<li className={styles["list-item"]}>
<span className={styles["list-item-title"]}>
Clone your project repository.
Clone your project repositories.
</span>
<p className={styles["list-item-text"]}>
Use Git to clone your project repository to your local machine.
Use Git to clone your project repositories to your local
machine.
</p>
</li>

<li className={styles["list-item"]}>
<span className={styles["list-item-title"]}>
Save the following configuration file to your local machine and
add local paths to all of your repositories to it.
</span>

<Input
control={control}
errors={errors}
isLabelHidden
isReadOnly
label="Analytics script configuration"
name="analyticsScriptConfiguration"
placeholder="Need to generate API key"
rightIcon={
<IconButton
iconName="clipboard"
isDisabled={isCopyButtonDisabled}
label="Copy script configuration"
onClick={handleCopyAnalyticsScriptConfigurationClick}
/>
}
rowsCount={9}
/>
</li>

<li className={styles["list-item"]}>
<span className={styles["list-item-title"]}>
Prepare the script.
</span>
<p className={styles["list-item-text"]}>
Copy the command below and replace &lt;project-path-1&gt;,
&lt;project-path-2&gt;, ... placeholder with your local
repositories paths:
Copy the command below and replace &lt;config-path&gt; with the
path to your configuration file from the previous step.
</p>
<Input
control={control}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ const execAsync = promisify(exec);

const executeCommand = async (
command: string,
cwd: string,
): Promise<{ stderr: Buffer | string; stdout: Buffer | string }> => {
return await execAsync(command);
return await execAsync(command, { cwd });
};

export { executeCommand };
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -27,8 +29,10 @@ class BaseAnalyticsCli {

private async setupAutoStart(): Promise<void> {
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}`);
Expand All @@ -47,11 +51,22 @@ class BaseAnalyticsCli {

private setupCommands(): void {
this.program
.command("track <apiKey> <userId> <repoPaths...>")
.command("track <configPath>")
.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;
}
Expand All @@ -62,6 +77,8 @@ class BaseAnalyticsCli {
);

if (!project) {
this.logger.error("API key is not valid.");

return;
}

Expand All @@ -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,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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}"`;
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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 };
7 changes: 7 additions & 0 deletions scripts/analytics/src/libs/types/analytics-script-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type AnalyticsScriptConfig = {
apiKey: string;
repoPaths: string[];
userId: number;
};

export { type AnalyticsScriptConfig };
1 change: 1 addition & 0 deletions scripts/analytics/src/libs/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { type AnalyticsScriptConfig } from "./analytics-script-config.js";
export {
type ServerErrorDetail,
type ServerErrorResponse,
Expand Down
44 changes: 22 additions & 22 deletions scripts/analytics/src/modules/analytics/analytics.service.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -16,39 +19,28 @@ 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(
repoPath: string,
): Promise<ActivityLogCreateItemRequestDto[]> {
const stats: ActivityLogCreateItemRequestDto[] = [];
const shortLogResult = await executeCommand(
this.gitService.getShortLogCommand(repoPath, "midnight"),
this.gitService.getShortLogCommand("midnight"),
repoPath,
);

const commitItems: CommitStatistics[] = [];
Expand Down Expand Up @@ -79,15 +71,23 @@ class AnalyticsService {
}

private async fetchRepository(repoPath: string): Promise<void> {
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<AnalyticsScriptConfig> {
return JSON.parse(
await fs.readFile(this.configPath, "utf8"),
) as AnalyticsScriptConfig;
}

public async collectAndSendStats(): Promise<void> {
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)));
}
Expand All @@ -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.");
Expand Down

0 comments on commit 00acb5a

Please sign in to comment.