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 () => { 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(); const result = await server.services.offlineUsageReport.generateUsageReportCSV();
return { return {
@@ -104,17 +104,21 @@ export const offlineUsageReportDALFactory = (db: TDbClient) => {
// Calculate average secrets per project // Calculate average secrets per project
const secretsPerProject = (await db const secretsPerProject = (await db
.from(TableName.SecretV2) .from(`${TableName.SecretV2} as s`)
.select("folderId") .select("p.id as projectId")
.count("* as count") .count("s.id as count")
.groupBy("folderId")) as Array<{ folderId: string; count: string }>; .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 = const averageSecretsPerProject =
totalProjects > 0 secretsPerProject.length > 0
? secretsPerProject.reduce( ? 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 0
) / totalProjects ) / secretsPerProject.length
: 0; : 0;
return { return {
@@ -58,8 +58,6 @@ export const offlineUsageReportServiceFactory = ({
offlineUsageReportDAL.getSecretRotationMetrics() offlineUsageReportDAL.getSecretRotationMetrics()
]); ]);
const csvLines: string[] = [];
const headers = [ const headers = [
"Total Users", "Total Users",
"Admin Users", "Admin Users",
@@ -89,8 +87,6 @@ export const offlineUsageReportServiceFactory = ({
headers.push("Signature"); headers.push("Signature");
csvLines.push(headers.slice(0, -1).join(","));
const dataRow: (string | number)[] = [ const dataRow: (string | number)[] = [
userMetrics.totalUsers, userMetrics.totalUsers,
userMetrics.adminUsers, userMetrics.adminUsers,
@@ -114,14 +110,13 @@ export const offlineUsageReportServiceFactory = ({
dataRow.push(projectMetrics.projectsByType[type] || 0); dataRow.push(projectMetrics.projectsByType[type] || 0);
}); });
const tempCsvLines = [headers.slice(0, -1).join(","), dataRow.join(",")]; const headersWithoutSignature = headers.slice(0, -1);
const contentWithoutSignature = tempCsvLines.join("\n"); const contentWithoutSignature = [headersWithoutSignature.join(","), dataRow.join(",")].join("\n");
const signature = signReportContent(contentWithoutSignature, licenseId); const signature = signReportContent(contentWithoutSignature, licenseId);
dataRow.push(signature); dataRow.push(signature);
const finalCsvLines = [headers.join(","), dataRow.join(",")]; const csvContent = [headers.join(","), dataRow.join(",")].join("\n");
const csvContent = finalCsvLines.join("\n");
return { return {
csvContent, csvContent,
+3 -5
View File
@@ -8,7 +8,6 @@ import { adminQueryKeys, adminStandaloneKeys } from "./queries";
import { import {
RootKeyEncryptionStrategy, RootKeyEncryptionStrategy,
TCreateAdminUserDTO, TCreateAdminUserDTO,
TGenerateUsageReportDTO,
TInvalidateCacheDTO, TInvalidateCacheDTO,
TServerConfig, TServerConfig,
TUpdateServerConfigDTO, TUpdateServerConfigDTO,
@@ -197,11 +196,10 @@ export const useInvalidateCache = () => {
}; };
export const useGenerateUsageReport = () => { export const useGenerateUsageReport = () => {
return useMutation<TUsageReportResponse, object, TGenerateUsageReportDTO>({ return useMutation<TUsageReportResponse, object, void>({
mutationFn: async (dto) => { mutationFn: async () => {
const { data } = await apiRequest.post<TUsageReportResponse>( const { data } = await apiRequest.post<TUsageReportResponse>(
"/api/v1/admin/usage-report/generate", "/api/v1/admin/usage-report/generate"
dto
); );
return data; return data;
} }
-4
View File
@@ -144,10 +144,6 @@ export interface TGetEnvOverrides {
}; };
} }
export type TGenerateUsageReportDTO = {
periodDays?: number;
};
export type TUsageReportResponse = { export type TUsageReportResponse = {
filename: string; filename: string;
csvContent: string; csvContent: string;
@@ -1,28 +1,14 @@
import { useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form";
import { faDownload, faFileAlt, faSpinner } from "@fortawesome/free-solid-svg-icons"; import { faDownload, faFileAlt, faSpinner } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, Card, CardTitle } from "@app/components/v2"; import { Button, Card, CardTitle } from "@app/components/v2";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
const formSchema = z.object({});
type TUsageReportForm = z.infer<typeof formSchema>;
export const UsageReportSection = () => { export const UsageReportSection = () => {
const [isGenerating, setIsGenerating] = useState(false); 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 downloadFile = (content: string, filename: string, mimeType: string = "text/csv") => {
const blob = new Blob([content], { type: mimeType }); const blob = new Blob([content], { type: mimeType });
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
@@ -35,7 +21,7 @@ export const UsageReportSection = () => {
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
}; };
const onSubmit = async () => { const handleGenerateReport = async () => {
try { try {
setIsGenerating(true); setIsGenerating(true);
@@ -59,8 +45,6 @@ export const UsageReportSection = () => {
} }
}; };
const isLoading = isSubmitting || isGenerating;
return ( return (
<Card className="p-6"> <Card className="p-6">
<CardTitle className="mb-4 flex items-center gap-3"> <CardTitle className="mb-4 flex items-center gap-3">
@@ -75,15 +59,14 @@ export const UsageReportSection = () => {
trail. trail.
</div> </div>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> <Button
<Button onClick={handleGenerateReport}
type="submit" className="w-fit"
isLoading={isLoading} isLoading={isGenerating}
leftIcon={<FontAwesomeIcon icon={isLoading ? faSpinner : faDownload} />} leftIcon={<FontAwesomeIcon icon={isGenerating ? faSpinner : faDownload} />}
> >
{isLoading ? "Generating..." : "Generate Report"} {isGenerating ? "Generating..." : "Generate Report"}
</Button> </Button>
</form>
</Card> </Card>
); );
}; };