Skip to content

Commit f738a42

Browse files
committed
feat: filter invocations by error/status (fixes #20)
1 parent 759b9dd commit f738a42

3 files changed

Lines changed: 135 additions & 56 deletions

File tree

packages/api/src/routes/explore/index.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,37 @@ app.get("/functions/:region/:name", async (c) => {
5959
return c.json(Items?.[0]);
6060
});
6161

62+
app.get("/functions/:region/:name/invocation-summaries", async (c) => {
63+
const [start, end] = getDates(c);
64+
const startTs = start.getTime();
65+
const endTs = end.getTime();
66+
67+
const params = {
68+
KeyConditionExpression: "#pk = :pk AND #sk BETWEEN :skStart AND :skEnd",
69+
ExpressionAttributeNames: {
70+
"#pk": "pk",
71+
"#sk": "sk",
72+
"#resultSummary": "resultSummary",
73+
},
74+
ExpressionAttributeValues: {
75+
":pk": `function#${c.req.param("region")}#${c.req.param("name")}`,
76+
":skStart": `invocation#${startTs}`,
77+
":skEnd": `invocation#${endTs}`,
78+
},
79+
ProjectionExpression: "#resultSummary",
80+
Limit: 10000,
81+
ScanIndexForward: false,
82+
};
83+
84+
const { Items } = await query(params);
85+
86+
return c.json(
87+
Array.from(new Set(Items.map((item) => item.resultSummary))).filter(
88+
Boolean,
89+
),
90+
);
91+
});
92+
6293
app.get("/functions/:region/:name/invocations", async (c) => {
6394
const [start, end] = getDates(c);
6495
const startTs = start.getTime();
@@ -91,7 +122,8 @@ app.get("/functions/:region/:name/invocations", async (c) => {
91122
ScanIndexForward: false,
92123
};
93124

94-
const resultSummaryFilters = c.req.query("resultSummaryFilters")?.split(',') || [];
125+
const resultSummaryFilters =
126+
c.req.query("resultSummaryFilters")?.split(",") || [];
95127
if (resultSummaryFilters.length) {
96128
const filterExpression = [];
97129
for (const filter of resultSummaryFilters) {

packages/dashboard/src/routes/invocations/page.tsx

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,13 @@
1+
import { Suspense } from "react";
12
import { Link, useParams } from "react-router-dom";
23

34
import { Button } from "@/components/ui/button";
4-
import { DataTable } from "@/components/tables/data-table";
5-
import { StatsChart } from "@/components/stats/stats-chart";
65
import FunctionSummary from "@/components/stats/function-summary";
7-
import { columns } from "./columns";
8-
import { useData } from "@/lib/api";
9-
import { useState } from "react";
6+
import { StatsChart } from "@/components/stats/stats-chart";
7+
import { InvocationsTable } from "@/routes/invocations/table";
108

119
const Invocations = () => {
1210
const { region, name } = useParams();
13-
const [startKey, setStartKey] = useState("");
14-
const [previousKeys, setPreviousKeys] = useState<string[]>([]);
15-
16-
const {
17-
data: { invocations, nextStartKey },
18-
} = useData(
19-
`functions/${region}/${name}/invocations?startKey=${encodeURIComponent(startKey)}`,
20-
{
21-
suspense: true,
22-
},
23-
);
24-
25-
const goBack = () => {
26-
setStartKey(previousKeys.pop());
27-
setPreviousKeys(previousKeys);
28-
};
29-
const goNext = () => {
30-
setPreviousKeys([...previousKeys, startKey]);
31-
setStartKey(nextStartKey);
32-
};
3311

3412
return (
3513
<div>
@@ -65,36 +43,9 @@ const Invocations = () => {
6543
/>
6644
</div>
6745
</div>
68-
<DataTable
69-
id="invocations"
70-
pageSize={50}
71-
columns={columns}
72-
data={invocations}
73-
defaultSorting={[{ id: "started", desc: true }]}
74-
/>
75-
<div className="flex items-center justify-between">
76-
<div className="text-xs text-muted-foreground">
77-
Page {previousKeys.length + 1} ({invocations.length} items)
78-
</div>
79-
<div className="flex items-center justify-end space-x-2 py-4">
80-
<Button
81-
variant="outline"
82-
size="sm"
83-
onClick={() => goBack()}
84-
disabled={!previousKeys.length}
85-
>
86-
Previous
87-
</Button>
88-
<Button
89-
variant="outline"
90-
size="sm"
91-
onClick={() => goNext()}
92-
disabled={!nextStartKey}
93-
>
94-
Next
95-
</Button>
96-
</div>
97-
</div>
46+
<Suspense fallback={<p>Loading...</p>}>
47+
<InvocationsTable region={region} name={name} />
48+
</Suspense>
9849
</div>
9950
);
10051
};
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { useState } from "react";
2+
3+
import { DataTable } from "@/components/tables/data-table";
4+
import { Button } from "@/components/ui/button";
5+
6+
import { columns } from "@/routes/invocations/columns";
7+
import { useData } from "@/lib/api";
8+
import { CheckIcon, XIcon } from "lucide-react";
9+
import { DataTableFilter } from "@/components/tables/data-table-filter";
10+
import { useUserState } from "@/lib/user-state";
11+
12+
export const InvocationsTable = ({ region, name }) => {
13+
const [startKey, setStartKey] = useState("");
14+
const [previousKeys, setPreviousKeys] = useState<string[]>([]);
15+
const [resultSummaryFilters, setResultSummaryFilters] = useUserState(
16+
`resultSummaryFilters-${region}-${name}`,
17+
[],
18+
);
19+
20+
let url = `functions/${region}/${name}/invocations`;
21+
url += `?startKey=${encodeURIComponent(startKey)}`;
22+
if (resultSummaryFilters?.length) {
23+
url += `&resultSummaryFilters=${encodeURIComponent(resultSummaryFilters.join(","))}`;
24+
}
25+
26+
const {
27+
data: { invocations, nextStartKey },
28+
} = useData(url, { suspense: true });
29+
30+
const { data: resultSummaryFilterOptions } = useData(
31+
`functions/${region}/${name}/invocation-summaries`,
32+
);
33+
34+
const goBack = () => {
35+
setStartKey(previousKeys.pop());
36+
setPreviousKeys(previousKeys);
37+
};
38+
const goNext = () => {
39+
setPreviousKeys([...previousKeys, startKey]);
40+
setStartKey(nextStartKey);
41+
};
42+
43+
const column = {
44+
getFilterValue: () => resultSummaryFilters,
45+
setFilterValue: setResultSummaryFilters,
46+
};
47+
48+
const options = resultSummaryFilterOptions?.map((value) => ({
49+
value,
50+
label: value,
51+
icon: value.startsWith("Successful") ? CheckIcon : XIcon,
52+
}));
53+
54+
return (
55+
<>
56+
<DataTable
57+
id="invocations"
58+
pageSize={50}
59+
columns={columns}
60+
data={invocations}
61+
defaultSorting={[{ id: "started", desc: true }]}
62+
>
63+
{() => (
64+
<DataTableFilter
65+
column={column}
66+
title="Invocation status"
67+
options={options}
68+
/>
69+
)}
70+
</DataTable>
71+
<div className="flex items-center justify-between">
72+
<div className="text-xs text-muted-foreground">
73+
Page {previousKeys.length + 1} ({invocations.length} items)
74+
</div>
75+
<div className="flex items-center justify-end space-x-2 py-4">
76+
<Button
77+
variant="outline"
78+
size="sm"
79+
onClick={() => goBack()}
80+
disabled={!previousKeys.length}
81+
>
82+
Previous
83+
</Button>
84+
<Button
85+
variant="outline"
86+
size="sm"
87+
onClick={() => goNext()}
88+
disabled={!nextStartKey}
89+
>
90+
Next
91+
</Button>
92+
</div>
93+
</div>
94+
</>
95+
);
96+
};

0 commit comments

Comments
 (0)