Address greptile comments

This commit is contained in:
Carlos Monastyrski
2025-09-01 21:18:48 -03:00
parent 4063cf5294
commit 14473c742c
6 changed files with 26 additions and 57 deletions
@@ -863,13 +863,6 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
});
},
handler: async () => {
const cfg = getConfig();
if (!cfg.LICENSE_KEY_OFFLINE) {
throw new BadRequestError({
message: "Offline usage reports are not enabled. LICENSE_KEY_OFFLINE must be configured."
});
}
const result = await server.services.offlineUsageReport.generateUsageReportCSV();
return {
@@ -104,17 +104,21 @@ export const offlineUsageReportDALFactory = (db: TDbClient) => {
// Calculate average secrets per project
const secretsPerProject = (await db
.from(TableName.SecretV2)
.select("folderId")
.count("* as count")
.groupBy("folderId")) as Array<{ folderId: string; count: string }>;
.from(`${TableName.SecretV2} as s`)
.select("p.id as projectId")
.count("s.id as count")
.leftJoin(`${TableName.SecretFolder} as sf`, "s.folderId", "sf.id")
.leftJoin(`${TableName.Environment} as e`, "sf.envId", "e.id")
.leftJoin(`${TableName.Project} as p`, "e.projectId", "p.id")
.groupBy("p.id")
.whereNotNull("p.id")) as Array<{ projectId: string; count: string }>;
const averageSecretsPerProject =
totalProjects > 0
secretsPerProject.length > 0
? secretsPerProject.reduce(
(sum, row: { folderId: string; count: string }) => sum + parseInt(row.count, 10),
(sum, row: { projectId: string; count: string }) => sum + parseInt(row.count, 10),
0
) / totalProjects
) / secretsPerProject.length
: 0;
return {
@@ -58,8 +58,6 @@ export const offlineUsageReportServiceFactory = ({
offlineUsageReportDAL.getSecretRotationMetrics()
]);
const csvLines: string[] = [];
const headers = [
"Total Users",
"Admin Users",
@@ -89,8 +87,6 @@ export const offlineUsageReportServiceFactory = ({
headers.push("Signature");
csvLines.push(headers.slice(0, -1).join(","));
const dataRow: (string | number)[] = [
userMetrics.totalUsers,
userMetrics.adminUsers,
@@ -114,14 +110,13 @@ export const offlineUsageReportServiceFactory = ({
dataRow.push(projectMetrics.projectsByType[type] || 0);
});
const tempCsvLines = [headers.slice(0, -1).join(","), dataRow.join(",")];
const contentWithoutSignature = tempCsvLines.join("\n");
const headersWithoutSignature = headers.slice(0, -1);
const contentWithoutSignature = [headersWithoutSignature.join(","), dataRow.join(",")].join("\n");
const signature = signReportContent(contentWithoutSignature, licenseId);
dataRow.push(signature);
const finalCsvLines = [headers.join(","), dataRow.join(",")];
const csvContent = finalCsvLines.join("\n");
const csvContent = [headers.join(","), dataRow.join(",")].join("\n");
return {
csvContent,
+3 -5
View File
@@ -8,7 +8,6 @@ import { adminQueryKeys, adminStandaloneKeys } from "./queries";
import {
RootKeyEncryptionStrategy,
TCreateAdminUserDTO,
TGenerateUsageReportDTO,
TInvalidateCacheDTO,
TServerConfig,
TUpdateServerConfigDTO,
@@ -197,11 +196,10 @@ export const useInvalidateCache = () => {
};
export const useGenerateUsageReport = () => {
return useMutation<TUsageReportResponse, object, TGenerateUsageReportDTO>({
mutationFn: async (dto) => {
return useMutation<TUsageReportResponse, object, void>({
mutationFn: async () => {
const { data } = await apiRequest.post<TUsageReportResponse>(
"/api/v1/admin/usage-report/generate",
dto
"/api/v1/admin/usage-report/generate"
);
return data;
}
-4
View File
@@ -144,10 +144,6 @@ export interface TGetEnvOverrides {
};
}
export type TGenerateUsageReportDTO = {
periodDays?: number;
};
export type TUsageReportResponse = {
filename: string;
csvContent: string;
@@ -1,28 +1,14 @@
import { useState } from "react";
import { useForm } from "react-hook-form";
import { faDownload, faFileAlt, faSpinner } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, Card, CardTitle } from "@app/components/v2";
import { apiRequest } from "@app/config/request";
const formSchema = z.object({});
type TUsageReportForm = z.infer<typeof formSchema>;
export const UsageReportSection = () => {
const [isGenerating, setIsGenerating] = useState(false);
const {
handleSubmit,
formState: { isSubmitting }
} = useForm<TUsageReportForm>({
resolver: zodResolver(formSchema)
});
const downloadFile = (content: string, filename: string, mimeType: string = "text/csv") => {
const blob = new Blob([content], { type: mimeType });
const url = window.URL.createObjectURL(blob);
@@ -35,7 +21,7 @@ export const UsageReportSection = () => {
window.URL.revokeObjectURL(url);
};
const onSubmit = async () => {
const handleGenerateReport = async () => {
try {
setIsGenerating(true);
@@ -59,8 +45,6 @@ export const UsageReportSection = () => {
}
};
const isLoading = isSubmitting || isGenerating;
return (
<Card className="p-6">
<CardTitle className="mb-4 flex items-center gap-3">
@@ -75,15 +59,14 @@ export const UsageReportSection = () => {
trail.
</div>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<Button
type="submit"
isLoading={isLoading}
leftIcon={<FontAwesomeIcon icon={isLoading ? faSpinner : faDownload} />}
>
{isLoading ? "Generating..." : "Generate Report"}
</Button>
</form>
<Button
onClick={handleGenerateReport}
className="w-fit"
isLoading={isGenerating}
leftIcon={<FontAwesomeIcon icon={isGenerating ? faSpinner : faDownload} />}
>
{isGenerating ? "Generating..." : "Generate Report"}
</Button>
</Card>
);
};