Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/modules/api/components/ApiKeyLogsSummary.tsx
T

193 lines
7.4 KiB
TypeScript

import { Link, useParams, useSearchParams } from "react-router";
import { useEffect, useState } from "react";
import ApiKeyLogUtils from "../utils/ApiKeyLogUtils";
import { RowHeaderDisplayDto } from "~/application/dtos/data/RowHeaderDisplayDto";
import { ApiCallSummaryDto } from "../dtos/ApiCallSummaryDto";
import ApiKeyLogsConstants from "../utils/ApiKeyLogsConstants";
import { FilterablePropertyDto } from "~/application/dtos/data/FilterablePropertyDto";
import { FilterableValueLink } from "~/components/ui/links/FilterableValueLink";
import WarningBanner from "~/components/ui/banners/WarningBanner";
import InputCombobox from "~/components/ui/input/InputCombobox";
import InputFilters from "~/components/ui/input/InputFilters";
import TableSimple from "~/components/ui/tables/TableSimple";
import { useLocaleFormatters } from "~/locale/format";
import UrlUtils from "~/utils/app/UrlUtils";
import ApiUtils from "~/utils/app/ApiUtils";
import SimpleBadge from "~/components/ui/badges/SimpleBadge";
import { Colors } from "~/application/enums/shared/Colors";
import ApiCallSpeedBadge from "./ApiCallSpeedBadge";
import { useTranslation } from "react-i18next";
interface Props {
data: {
items: ApiCallSummaryDto[];
allTenants: { id: string; name: string }[];
filterableProperties: FilterablePropertyDto[];
};
}
export default function ApiKeyLogsSummary({ data }: Props) {
const { t } = useTranslation();
const formatters = useLocaleFormatters();
const params = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const [groupBy, setGroupBy] = useState<string[]>(ApiKeyLogUtils.getGroupByValues(searchParams));
const [headers, setHeaders] = useState<RowHeaderDisplayDto<ApiCallSummaryDto>[]>([]);
useEffect(() => {
if (ApiKeyLogUtils.getGroupByValues(searchParams).sort().join(",") !== groupBy.sort().join(",")) {
searchParams.delete("groupBy");
groupBy.forEach((by) => {
searchParams.append("groupBy", by);
});
setSearchParams(searchParams);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [groupBy]);
useEffect(() => {
const headers: RowHeaderDisplayDto<ApiCallSummaryDto>[] = [];
let currentGroupBy = groupBy;
if (currentGroupBy.length === 0) {
currentGroupBy = ApiKeyLogsConstants.DEFAULT_GROUP_BY;
}
const getTenant = (item: ApiCallSummaryDto) => {
return data.allTenants.find((x) => x.id === item.tenantId);
};
const getCountLink = (item: ApiCallSummaryDto) => {
const searchParams = new URLSearchParams();
// searchParams.set("pageSize", "100");
currentGroupBy.forEach((groupBy) => {
if (groupBy === "method") {
searchParams.append("method", item.method);
} else if (groupBy === "endpoint") {
searchParams.append("endpoint", item.endpoint);
} else if (groupBy === "params") {
searchParams.append("params", item.params);
} else if (groupBy === "status") {
searchParams.append("status", item.status?.toString() ?? "{null}");
}
});
return UrlUtils.getModulePath(params, `api/logs?${searchParams.toString()}`);
};
if (groupBy.includes("method")) {
headers.push({
name: "method",
title: t("models.apiKeyLog.method"),
value: (item) => (
<FilterableValueLink name="method" value={item.method}>
<SimpleBadge title={item.method} color={ApiUtils.getMethodColor(item.method)} underline />
</FilterableValueLink>
),
});
}
if (groupBy.includes("endpoint")) {
headers.push({
name: "endpoint",
title: t("models.apiKeyLog.endpoint"),
value: (item) => <FilterableValueLink name="endpoint" value={item.endpoint} />,
});
}
if (groupBy.includes("params")) {
headers.push({
name: "params",
title: t("models.apiKeyLog.params"),
value: (item) => <FilterableValueLink name="params" value={item.params} />,
});
}
if (groupBy.includes("status")) {
headers.push({
name: "status",
title: t("models.apiKeyLog.status"),
value: (item) => (
<FilterableValueLink name="status" value={item.status?.toString() ?? "{null}"}>
<SimpleBadge title={item.status?.toString() ?? t("shared.unknown")} color={item.status?.toString().startsWith("4") ? Colors.RED : Colors.GREEN} />
</FilterableValueLink>
),
});
}
if (groupBy.includes("tenantId")) {
headers.push({
name: "tenantId",
title: t("models.apiKeyLog.tenant"),
value: (item) => <FilterableValueLink name="tenantId" value={getTenant(item)?.name ?? ""} />,
});
}
headers.push({
name: "apiCalls",
title: t("models.apiKeyLog.apiCalls"),
align: "right",
value: (item) => (
<div className="flex justify-end text-right">
<Link to={getCountLink(item)} className="hover:underline">
{t("models.apiKeyLog.calls", { count: Number(item._count._all), formattedCount: formatters.number(Number(item._count._all)) })}
</Link>
</div>
),
});
headers.push({
name: "duration",
title: t("models.apiKeyLog.avgDuration"),
value: (item) =>
item._avg.duration === null ? (
<span className="text-muted-foreground text-xs italic">{t("shared.notAvailable")}</span>
) : (
<div>{formatters.number(Number(item._avg.duration), { style: "unit", unit: "millisecond", unitDisplay: "short", maximumFractionDigits: 3 })}</div>
),
});
headers.push({
name: "speed",
title: t("models.apiKeyLog.speed"),
value: (item) =>
item._avg.duration === null ? (
<span className="text-muted-foreground text-xs italic">{t("shared.notAvailable")}</span>
) : (
<ApiCallSpeedBadge duration={Number(item._avg.duration)} />
),
});
headers.push({
name: "actions",
title: "",
value: (item) => (
<Link to={getCountLink(item)} className="hover:underline">
{t("shared.details")} <span className="ml-1">&rarr;</span>
</Link>
),
});
setHeaders(headers);
}, [data, groupBy, params, t]);
return (
<div className="space-y-2">
<div className="flex w-full items-center space-x-2">
<InputCombobox
name="groupBy"
prefix={t("models.apiKeyLog.groupBy.prefix")}
className="bg-background h-8"
selectPlaceholder={t("models.apiKeyLog.groupBy.placeholder")}
options={[
{ value: "method", name: t("models.apiKeyLog.method") },
{ value: "endpoint", name: t("models.apiKeyLog.endpoint") },
{ value: "params", name: t("models.apiKeyLog.params") },
{ value: "status", name: t("models.apiKeyLog.status") },
{ value: "tenantId", name: t("models.apiKeyLog.tenant") },
]}
value={groupBy}
onChange={(value) => {
setGroupBy(value as string[]);
}}
/>
<div className="grow">{/* <InputSearchWithURL /> */}</div>
<InputFilters size="sm" filters={data.filterableProperties} />
</div>
{groupBy.length === 0 ? (
<WarningBanner title={t("models.apiKeyLog.groupBy.title")} text={t("models.apiKeyLog.groupBy.required")} />
) : (
<TableSimple items={data.items} headers={headers} />
)}
</div>
);
}