-
Notifications
You must be signed in to change notification settings - Fork 25
chore: live tail mode for config changes #2980
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import { useGetAllConfigsChangesQuery } from "@flanksource-ui/api/query-hooks/useConfigChangesHooks"; | ||
| import { ConfigChange } from "@flanksource-ui/api/types/configs"; | ||
| import { ConfigChangeTable } from "@flanksource-ui/components/Configs/Changes/ConfigChangeTable"; | ||
| import { ConfigChangeFilters } from "@flanksource-ui/components/Configs/Changes/ConfigChangesFilters/ConfigChangesFilters"; | ||
| import ConfigPageTabs from "@flanksource-ui/components/Configs/ConfigPageTabs"; | ||
|
|
@@ -12,9 +13,22 @@ import { | |
| import { Head } from "@flanksource-ui/ui/Head"; | ||
| import { SearchLayout } from "@flanksource-ui/ui/Layout/SearchLayout"; | ||
| import { refreshButtonClickedTrigger } from "@flanksource-ui/ui/SlidingSideBar/SlidingSideBar"; | ||
| import { Toggle } from "@flanksource-ui/ui/FormControls/Toggle"; | ||
| import { useAtom } from "jotai"; | ||
| import { useEffect, useState } from "react"; | ||
| import { useSearchParams } from "react-router-dom"; | ||
|
|
||
| function getNewestCreatedAt(changes: ConfigChange[]): string | undefined { | ||
| let latest: string | undefined; | ||
| for (const c of changes) { | ||
| const ts = typeof c.created_at === "string" ? c.created_at : undefined; | ||
| if (ts && (!latest || ts > latest)) { | ||
| latest = ts; | ||
| } | ||
| } | ||
| return latest; | ||
| } | ||
|
|
||
| export function ConfigChangesPage() { | ||
| const [, setRefreshButtonClickedTrigger] = useAtom( | ||
| refreshButtonClickedTrigger | ||
|
|
@@ -31,22 +45,83 @@ export function ConfigChangesPage() { | |
|
|
||
| const pageSize = params.get("pageSize") ?? "200"; | ||
|
|
||
| const [liveTail, setLiveTail] = useState(false); | ||
| const [tailCursor, setTailCursor] = useState<string | undefined>(undefined); | ||
| const [tailedChanges, setTailedChanges] = useState<ConfigChange[]>([]); | ||
|
|
||
| const { data, isLoading, error, isRefetching, refetch } = | ||
| useGetAllConfigsChangesQuery({ | ||
| keepPreviousData: true | ||
| }); | ||
|
|
||
| const changes = (data?.changes ?? []).map((changes) => ({ | ||
| ...changes, | ||
| // Initialize cursor from base data when live tail is turned on | ||
| useEffect(() => { | ||
| if (liveTail && data?.changes?.length && !tailCursor) { | ||
| setTailCursor(getNewestCreatedAt(data.changes)); | ||
| } | ||
|
Comment on lines
+57
to
+61
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Live mode is incorrect on page 2+.
🤖 Prompt for AI Agents |
||
| }, [liveTail, data, tailCursor]); | ||
|
|
||
| // Reset when live tail is turned off | ||
| useEffect(() => { | ||
| if (!liveTail) { | ||
| setTailedChanges([]); | ||
| setTailCursor(undefined); | ||
| } | ||
| }, [liveTail]); | ||
|
Comment on lines
+64
to
+70
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reset tail state when the query scope changes. The tail state is only cleared when Also applies to: 71-76 |
||
|
|
||
| const { data: pollData } = useGetAllConfigsChangesQuery({ | ||
| cursor: tailCursor, | ||
| keepPreviousData: false, | ||
| enabled: liveTail && !!tailCursor, | ||
| refetchInterval: liveTail ? 5000 : false | ||
| }); | ||
|
|
||
| // Accumulate new items from poll and advance cursor | ||
| useEffect(() => { | ||
| if (!pollData?.changes?.length) return; | ||
|
|
||
| const incoming = pollData.changes; | ||
| const newest = getNewestCreatedAt(incoming); | ||
| if (newest) { | ||
| setTailCursor((prev) => (!prev || newest > prev ? newest : prev)); | ||
| } | ||
|
|
||
| setTailedChanges((prev) => { | ||
| const incomingIds = new Set(incoming.map((c) => c.id)); | ||
| const filtered = prev.filter((c) => !incomingIds.has(c.id)); | ||
| return [...incoming, ...filtered]; | ||
| }); | ||
| }, [pollData]); | ||
|
|
||
| const baseChanges = (data?.changes ?? []).map((change) => ({ | ||
| ...change, | ||
| config: { | ||
| id: change.config_id!, | ||
| type: change.type!, | ||
| name: change.name!, | ||
| deleted_at: change.deleted_at | ||
| } | ||
| })); | ||
|
|
||
| const tailedWithConfig = tailedChanges.map((change) => ({ | ||
| ...change, | ||
| config: { | ||
| id: changes.config_id!, | ||
| type: changes.type!, | ||
| name: changes.name!, | ||
| deleted_at: changes.deleted_at | ||
| id: change.config_id!, | ||
| type: change.type!, | ||
| name: change.name!, | ||
| deleted_at: change.deleted_at | ||
| } | ||
| })); | ||
|
|
||
| const totalChanges = data?.total ?? 0; | ||
| const tailedIds = new Set(tailedWithConfig.map((c) => c.id)); | ||
| const baseWithoutTailed = baseChanges.filter((c) => !tailedIds.has(c.id)); | ||
| const baseIds = new Set(baseChanges.map((c) => c.id)); | ||
| const newTailedCount = tailedWithConfig.filter( | ||
| (c) => !baseIds.has(c.id) | ||
| ).length; | ||
| const changes = [...tailedWithConfig, ...baseWithoutTailed]; | ||
|
|
||
| const totalChanges = (data?.total ?? 0) + newTailedCount; | ||
| const totalChangesPages = Math.ceil(totalChanges / parseInt(pageSize)); | ||
|
|
||
| const errorMessage = | ||
|
|
@@ -99,7 +174,16 @@ export function ConfigChangesPage() { | |
| <InfoMessage message={errorMessage} /> | ||
| ) : ( | ||
| <> | ||
| <ConfigChangeFilters paramsToReset={["page"]} /> | ||
| <ConfigChangeFilters | ||
| paramsToReset={["page"]} | ||
| extra={ | ||
| <Toggle | ||
| label="Live" | ||
| value={liveTail} | ||
| onChange={setLiveTail} | ||
| /> | ||
| } | ||
| /> | ||
| <ConfigChangeTable | ||
| data={changes} | ||
| isLoading={isLoading} | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,24 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useGetConfigChangesByIDQuery } from "@flanksource-ui/api/query-hooks/useConfigChangesHooks"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ConfigChange } from "@flanksource-ui/api/types/configs"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ConfigChangeTable } from "@flanksource-ui/components/Configs/Changes/ConfigChangeTable"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ConfigRelatedChangesFilters } from "@flanksource-ui/components/Configs/Changes/ConfigsRelatedChanges/FilterBar/ConfigRelatedChangesFilters"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ConfigDetailsTabs } from "@flanksource-ui/components/Configs/ConfigDetailsTabs"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { InfoMessage } from "@flanksource-ui/components/InfoMessage"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Toggle } from "@flanksource-ui/ui/FormControls/Toggle"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useEffect, useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useParams, useSearchParams } from "react-router-dom"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function getNewestCreatedAt(changes: ConfigChange[]): string | undefined { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let latest: string | undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const c of changes) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ts = typeof c.created_at === "string" ? c.created_at : undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (ts && (!latest || ts > latest)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| latest = ts; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return latest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use Per the PR objectives, the delta fetch should use Suggested fix-function getNewestCreatedAt(changes: ConfigChange[]): string | undefined {
+function getNewestInsertedAt(changes: ConfigChange[]): string | undefined {
return changes.reduce<string | undefined>(
(latest, c) =>
- c.created_at && (!latest || c.created_at > latest)
- ? c.created_at
+ c.inserted_at && (!latest || c.inserted_at > latest)
+ ? c.inserted_at
: latest,
undefined
);
}Update the call sites at lines 43 and 67 accordingly. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function ConfigDetailsChangesPage() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { id } = useParams(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -15,21 +29,81 @@ export function ConfigDetailsChangesPage() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const pageSize = params.get("pageSize") ?? "200"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [liveTail, setLiveTail] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [tailCursor, setTailCursor] = useState<string | undefined>(undefined); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [tailedChanges, setTailedChanges] = useState<ConfigChange[]>([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data, isLoading, error, refetch } = useGetConfigChangesByIDQuery({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| keepPreviousData: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enabled: !!id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const changes = (data?.changes ?? []).map((changes) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...changes, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Initialize cursor from base data when live tail is turned on | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (liveTail && data?.changes?.length && !tailCursor) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTailCursor(getNewestCreatedAt(data.changes)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bootstrapping from This only seeds from the currently loaded slice, and the poll query will not run until Also applies to: 56-60 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [liveTail, data, tailCursor]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Reset when live tail is turned off | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!liveTail) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTailedChanges([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTailCursor(undefined); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [liveTail]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+48
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reset live-tail state when the query context changes. While Please clear/reseed the tail state whenever the query-shaping params change, or disable those controls while live mode is active. Also applies to: 123-128 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data: pollData } = useGetConfigChangesByIDQuery({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cursor: tailCursor, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| keepPreviousData: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enabled: liveTail && !!id && !!tailCursor, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| refetchInterval: liveTail ? 5000 : false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Accumulate new items from poll and advance cursor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!pollData?.changes?.length) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const incoming = pollData.changes; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newest = getNewestCreatedAt(incoming); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (newest) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTailCursor((prev) => (!prev || newest > prev ? newest : prev)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+56
to
+71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Live polling can permanently skip changes.
Please make the tail query ignore normal pagination, or drain all delta pages before moving the cursor. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTailedChanges((prev) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const incomingIds = new Set(incoming.map((c) => c.id)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const filtered = prev.filter((c) => !incomingIds.has(c.id)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [...incoming, ...filtered]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [pollData]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const baseChanges = (data?.changes ?? []).map((change) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...change, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: changes.config_id!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: changes.type!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: changes.name! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: change.config_id!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: change.type!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: change.name! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const totalChanges = data?.total ?? 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const tailedWithConfig = tailedChanges.map((change) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...change, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: change.config_id!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: change.type!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: change.name! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+80
to
+96
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-null assertions may cause runtime errors if API returns incomplete data. The code uses non-null assertions ( Consider adding fallback values or filtering out incomplete records: Proposed defensive handling const baseChanges = (data?.changes ?? []).map((change) => ({
...change,
config: {
- id: change.config_id!,
- type: change.type!,
- name: change.name!
+ id: change.config_id ?? "",
+ type: change.type ?? "",
+ name: change.name ?? ""
}
}));
const tailedWithConfig = tailedChanges.map((change) => ({
...change,
config: {
- id: change.config_id!,
- type: change.type!,
- name: change.name!
+ id: change.config_id ?? "",
+ type: change.type ?? "",
+ name: change.name ?? ""
}
}));📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const tailedIds = new Set(tailedWithConfig.map((c) => c.id)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const baseWithoutTailed = baseChanges.filter((c) => !tailedIds.has(c.id)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const baseIds = new Set(baseChanges.map((c) => c.id)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newTailedCount = tailedWithConfig.filter( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (c) => !baseIds.has(c.id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ).length; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const changes = [...tailedWithConfig, ...baseWithoutTailed]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const totalChanges = (data?.total ?? 0) + newTailedCount; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const totalChangesPages = Math.ceil(totalChanges / parseInt(pageSize)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -50,7 +124,12 @@ export function ConfigDetailsChangesPage() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className={`flex h-full flex-1 flex-col overflow-y-auto`}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex w-full flex-1 flex-col items-start gap-2 overflow-y-auto"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ConfigRelatedChangesFilters paramsToReset={["page"]} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ConfigRelatedChangesFilters | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| paramsToReset={["page"]} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| extra={ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Toggle label="Live" value={liveTail} onChange={setLiveTail} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex w-full flex-1 flex-col overflow-y-auto"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ConfigChangeTable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data={changes} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
inserted_atfor the tail cursor.The live stream is advancing on
created_at, but this feature is supposed to fetch deltas byinserted_at. With the current cursor, a later-inserted row whosecreated_atis older than the cursor is silently missed.Suggested fix
Also applies to: 58-60, 83-86
🤖 Prompt for AI Agents