Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions internal/portal/src/common/MetricsChart/useMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,85 @@ export function useMetrics({

return { data, error, isLoading };
}

export function useBatchedMetrics({
measures,
destinationIds,
timeframe,
filters,
granularity: granularityOverride,
}: {
measures: string[];
destinationIds: string[];
timeframe: Timeframe;
filters?: Record<string, string>;
granularity?: string;
}) {
const apiClient = useContext(ApiContext);

const measuresKey = measures.join(",");
const filtersKey = filters
? Object.entries(filters)
.map(([k, v]) => `${k}=${v}`)
.join(",")
: "";
const idsKey = [...destinationIds].sort().join(",");

const url = useMemo(() => {
if (destinationIds.length === 0) return null;

const { start, end } = getDateRange(timeframe);
const params = new URLSearchParams();
params.set("time[start]", start);
params.set("time[end]", end);

for (const m of measures) {
params.append("measures[]", m);
}

const sortedIds = [...destinationIds].sort();
for (const id of sortedIds) {
params.append("filters[destination_id][]", id);
}

params.append("dimensions[]", "destination_id");

if (filters) {
for (const [k, v] of Object.entries(filters)) {
params.set(`filters[${k}]`, v);
}
}

params.set(
"granularity",
granularityOverride ?? getGranularity(timeframe),
);

return `metrics/attempts?${params.toString()}`;
}, [idsKey, measuresKey, filtersKey, granularityOverride, timeframe]);

const { data, error, isLoading } = useSWR<MetricsResponse>(
url,
(path: string) => apiClient.fetchRoot(path),
{
refreshInterval: 60_000,
revalidateOnFocus: false,
},
);

const grouped = useMemo(() => {
if (!data) return undefined;

const result: Record<string, MetricsDataPoint[]> = {};
for (const point of data.data) {
const destId = point.dimensions.destination_id;
if (!result[destId]) {
result[destId] = [];
}
result[destId].push(point);
}
return result;
}, [data]);

return { data: grouped, error, isLoading };
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const DestinationMetrics: React.FC<DestinationMetricsProps> = ({
measures: ["count"],
destinationId: destination.id,
timeframe,
filters: { attempt_number: "0", manual: "false" },
filters: { attempt_number: "1", manual: "false" },
});

const delivery = useMetrics({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import Sparkline from "../../common/Sparkline/Sparkline";
import { useMetrics } from "../../common/MetricsChart/useMetrics";
import { MetricsDataPoint } from "../../common/MetricsChart/useMetrics";

interface DestinationEventsCellProps {
destinationId: string;
metricsData?: MetricsDataPoint[];
isLoading: boolean;
}

function formatCount(n: number): string {
Expand All @@ -12,21 +13,14 @@ function formatCount(n: number): string {
}

const DestinationEventsCell: React.FC<DestinationEventsCellProps> = ({
destinationId,
metricsData,
isLoading,
}) => {
const { data, isLoading } = useMetrics({
measures: ["successful_count", "failed_count"],
destinationId,
timeframe: "24h",
granularity: "4h",
filters: { attempt_number: "0", manual: "false" },
});

if (isLoading || !data) {
if (isLoading || !metricsData) {
return <span className="histogram-cell__loading"></span>;
}

const points = data.data.map((d) => ({
const points = metricsData.map((d) => ({
successful: d.metrics.successful_count ?? 0,
failed: d.metrics.failed_count ?? 0,
}));
Expand Down
22 changes: 20 additions & 2 deletions internal/portal/src/scenes/DestinationsList/DestinationList.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import "./DestinationList.scss";

import { useState } from "react";
import { useMemo, useState } from "react";
import useSWR from "swr";

import Badge from "../../common/Badge/Badge";
import Button from "../../common/Button/Button";
import { Checkbox } from "../../common/Checkbox/Checkbox";
import Dropdown from "../../common/Dropdown/Dropdown";
import { AddIcon, FilterIcon, Loading } from "../../common/Icons";
import { useBatchedMetrics } from "../../common/MetricsChart/useMetrics";
import SearchInput from "../../common/SearchInput/SearchInput";
import Table from "../../common/Table/Table";
import Tooltip from "../../common/Tooltip/Tooltip";
Expand All @@ -20,6 +21,20 @@ import DestinationEventsCell from "./DestinationEventsCell";
const DestinationList: React.FC = () => {
const { data: destinations } = useSWR<Destination[]>("destinations");
const destination_types = useDestinationTypes();

const destinationIds = useMemo(
() => destinations?.map((d) => d.id) ?? [],
[destinations],
);
const { data: batchedMetrics, isLoading: metricsLoading } =
useBatchedMetrics({
measures: ["successful_count", "failed_count"],
destinationIds,
timeframe: "24h",
granularity: "4h",
filters: { attempt_number: "1", manual: "false" },
});

const [searchTerm, setSearchTerm] = useState("");
const [selectedStatus, setSelectedStatus] = useState<Record<string, boolean>>(
{},
Expand Down Expand Up @@ -122,7 +137,10 @@ const DestinationList: React.FC = () => {
) : (
<Badge text="Active" success />
),
<DestinationEventsCell destinationId={destination.id} />,
<DestinationEventsCell
metricsData={batchedMetrics?.[destination.id]}
isLoading={metricsLoading}
/>,
].filter((entry) => entry !== null),
link: `/destinations/${destination.id}`,
})) || [];
Expand Down
Loading