♻️ set default log analytics time range to. 7days ago

This commit is contained in:
Fred KISSIE
2025-12-08 22:57:05 +01:00
parent 2325e30f26
commit e0a79b7d4d
3 changed files with 52 additions and 45 deletions

View File

@@ -2,7 +2,7 @@ import { db, requestAuditLog, driver } from "@server/db";
import { registry } from "@server/openApi"; import { registry } from "@server/openApi";
import { NextFunction } from "express"; import { NextFunction } from "express";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { eq, gt, lt, and, count, sql, desc, not, isNull } from "drizzle-orm"; import { eq, gte, lte, and, count, sql, desc, not, isNull } from "drizzle-orm";
import { OpenAPITags } from "@server/openApi"; import { OpenAPITags } from "@server/openApi";
import { z } from "zod"; import { z } from "zod";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -11,6 +11,14 @@ import { fromError } from "zod-validation-error";
import response from "@server/lib/response"; import response from "@server/lib/response";
import logger from "@server/logger"; import logger from "@server/logger";
function getSevenDaysAgo() {
const today = new Date();
today.setHours(0, 0, 0, 0); // Set to midnight
const sevenDaysAgo = new Date(today);
sevenDaysAgo.setDate(today.getDate() - 7);
return sevenDaysAgo.toISOString();
}
const queryAccessAuditLogsQuery = z.object({ const queryAccessAuditLogsQuery = z.object({
// iso string just validate its a parseable date // iso string just validate its a parseable date
timeStart: z timeStart: z
@@ -19,7 +27,8 @@ const queryAccessAuditLogsQuery = z.object({
error: "timeStart must be a valid ISO date string" error: "timeStart must be a valid ISO date string"
}) })
.transform((val) => Math.floor(new Date(val).getTime() / 1000)) .transform((val) => Math.floor(new Date(val).getTime() / 1000))
.optional(), .optional()
.prefault(getSevenDaysAgo),
timeEnd: z timeEnd: z
.string() .string()
.refine((val) => !isNaN(Date.parse(val)), { .refine((val) => !isNaN(Date.parse(val)), {
@@ -55,15 +64,10 @@ type Q = z.infer<typeof queryRequestAuditLogsCombined>;
async function query(query: Q) { async function query(query: Q) {
let baseConditions = and( let baseConditions = and(
eq(requestAuditLog.orgId, query.orgId), eq(requestAuditLog.orgId, query.orgId),
lt(requestAuditLog.timestamp, query.timeEnd) gte(requestAuditLog.timestamp, query.timeStart),
lte(requestAuditLog.timestamp, query.timeEnd)
); );
if (query.timeStart) {
baseConditions = and(
baseConditions,
gt(requestAuditLog.timestamp, query.timeStart)
);
}
if (query.resourceId) { if (query.resourceId) {
baseConditions = and( baseConditions = and(
baseConditions, baseConditions,

View File

@@ -1,22 +1,27 @@
"use client"; "use client";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { cn } from "@app/lib/cn";
import { createApiClient } from "@app/lib/api";
import { import {
logAnalyticsFiltersSchema, logAnalyticsFiltersSchema,
logQueries, logQueries,
resourceQueries resourceQueries
} from "@app/lib/queries"; } from "@app/lib/queries";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useState } from "react";
import { Card, CardContent, CardHeader } from "./ui/card";
import { LoaderIcon, RefreshCw, XIcon } from "lucide-react"; import { LoaderIcon, RefreshCw, XIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { DateRangePicker, type DateTimeValue } from "./DateTimePicker"; import { DateRangePicker, type DateTimeValue } from "./DateTimePicker";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { cn } from "@app/lib/cn"; import { Card, CardContent, CardHeader } from "./ui/card";
import { useTranslations } from "next-intl";
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
import {
InfoSection,
InfoSectionContent,
InfoSections,
InfoSectionTitle
} from "./InfoSection";
import { Label } from "./ui/label";
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -24,23 +29,10 @@ import {
SelectTrigger, SelectTrigger,
SelectValue SelectValue
} from "./ui/select"; } from "./ui/select";
import { Label } from "./ui/label";
import { Separator } from "./ui/separator"; import { Separator } from "./ui/separator";
import {
InfoSection,
InfoSectionContent,
InfoSections,
InfoSectionTitle
} from "./InfoSection";
import { WorldMap } from "./WorldMap"; import { WorldMap } from "./WorldMap";
import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji";
import { import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from "./ui/tooltip";
import { import {
ChartContainer, ChartContainer,
ChartLegend, ChartLegend,
@@ -49,12 +41,25 @@ import {
ChartTooltipContent, ChartTooltipContent,
type ChartConfig type ChartConfig
} from "./ui/chart"; } from "./ui/chart";
import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts"; import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from "./ui/tooltip";
export type AnalyticsContentProps = { export type AnalyticsContentProps = {
orgId: string; orgId: string;
}; };
function getSevenDaysAgo() {
const today = new Date();
today.setHours(0, 0, 0, 0); // Set to midnight
const sevenDaysAgo = new Date(today);
sevenDaysAgo.setDate(today.getDate() - 7);
return sevenDaysAgo;
}
export function LogAnalyticsData(props: AnalyticsContentProps) { export function LogAnalyticsData(props: AnalyticsContentProps) {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const path = usePathname(); const path = usePathname();
@@ -67,17 +72,18 @@ export function LogAnalyticsData(props: AnalyticsContentProps) {
const isEmptySearchParams = const isEmptySearchParams =
!filters.resourceId && !filters.timeStart && !filters.timeEnd; !filters.resourceId && !filters.timeStart && !filters.timeEnd;
const env = useEnvContext();
const [api] = useState(() => createApiClient(env));
const router = useRouter(); const router = useRouter();
console.log({ filters });
const dateRange = { const dateRange = {
startDate: filters.timeStart ? new Date(filters.timeStart) : undefined, startDate: filters.timeStart
endDate: filters.timeEnd ? new Date(filters.timeEnd) : undefined ? new Date(filters.timeStart)
: getSevenDaysAgo(),
endDate: filters.timeEnd ? new Date(filters.timeEnd) : new Date()
}; };
const { data: resources = [], isFetching: isFetchingResources } = useQuery( const { data: resources = [], isFetching: isFetchingResources } = useQuery(
resourceQueries.listNamesPerOrg(props.orgId, api) resourceQueries.listNamesPerOrg(props.orgId)
); );
const { const {
@@ -88,7 +94,6 @@ export function LogAnalyticsData(props: AnalyticsContentProps) {
} = useQuery( } = useQuery(
logQueries.requestAnalytics({ logQueries.requestAnalytics({
orgId: props.orgId, orgId: props.orgId,
api,
filters filters
}) })
); );

View File

@@ -168,17 +168,15 @@ export type LogAnalyticsFilters = z.TypeOf<typeof logAnalyticsFiltersSchema>;
export const logQueries = { export const logQueries = {
requestAnalytics: ({ requestAnalytics: ({
orgId, orgId,
filters, filters
api
}: { }: {
orgId: string; orgId: string;
filters: LogAnalyticsFilters; filters: LogAnalyticsFilters;
api: AxiosInstance;
}) => }) =>
queryOptions({ queryOptions({
queryKey: ["REQUEST_LOG_ANALYTICS", orgId, filters] as const, queryKey: ["REQUEST_LOG_ANALYTICS", orgId, filters] as const,
queryFn: async ({ signal }) => { queryFn: async ({ signal, meta }) => {
const res = await api.get< const res = await meta!.api.get<
AxiosResponse<QueryRequestAnalyticsResponse> AxiosResponse<QueryRequestAnalyticsResponse>
>(`/org/${orgId}/logs/analytics`, { >(`/org/${orgId}/logs/analytics`, {
params: filters, params: filters,
@@ -228,11 +226,11 @@ export const resourceQueries = {
return res.data.data.clients; return res.data.data.clients;
} }
}), }),
listNamesPerOrg: (orgId: string, api: AxiosInstance) => listNamesPerOrg: (orgId: string) =>
queryOptions({ queryOptions({
queryKey: ["RESOURCES_NAMES", orgId] as const, queryKey: ["RESOURCES_NAMES", orgId] as const,
queryFn: async ({ signal }) => { queryFn: async ({ signal, meta }) => {
const res = await api.get< const res = await meta!.api.get<
AxiosResponse<ListResourceNamesResponse> AxiosResponse<ListResourceNamesResponse>
>(`/org/${orgId}/resource-names`, { >(`/org/${orgId}/resource-names`, {
signal signal