diff --git a/CHANGELOG.md b/CHANGELOG.md index ddc1f5dd..9305290b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Optimized `ChannelTableStreams` (the expanded stream list inside each channel row) to reduce mount cost: moved pure helper functions and static values (`getCoreRowModel`, `defaultColumn`, stat categorization/formatting) outside the component so they're created once; stabilized the TanStack column definitions by removing `data` and `expandedAdvancedStats` from the `useMemo` dependency array (cell renderers receive the row at render time); switched advanced-stats toggle tracking from `useState` to a `useRef` + per-cell local state so toggling one stream's stats doesn't recreate the entire column array and table instance; memoized `dataIds`, `removeStream`, `handleDragEnd`, and `handleWatchStream` with `useMemo`/`useCallback`; extracted `StreamInfoCell` as a `React.memo` component with its own memoized stat categorization. - Fixed `getChannelStreams` store selector to return a stable empty-array reference instead of creating a new `[]` on every call for channels without streams, preventing unnecessary re-renders via the `shallow` comparator. - Memoized individual rows in `CustomTableBody` so that expanding/collapsing a channel only re-renders the 1-2 affected rows instead of all rows on the page. Callback functions (`renderBodyCell`, `expandedRowRenderer`) are stored in refs so memoized rows always use the latest version without the function references themselves defeating the memo comparator. + - Stream reorder in `ChannelTableStreams` now completes in a single PATCH request instead of three. Previously dragging a stream to a new position triggered a PATCH, then `requeryStreams()` (re-fetching all streams), then `requeryChannels()` (re-fetching the entire paginated channel list with all embedded stream objects). The reorder now uses a dedicated `API.reorderChannelStreams()` path that issues only the PATCH, then updates the store in-place by reordering the existing stream objects without any network round-trips. On failure, `requeryChannels()` is called to restore correct state. + - Fixed N+1 `UPDATE` queries in the stream-order write path. `ChannelSerializer.update()` and the bulk-edit view were calling `ChannelStream.save(update_fields=["order"])` once per stream whose position changed. Both now collect all modified `ChannelStream` objects and issue a single `ChannelStream.objects.bulk_update(…, ["order"])` call. Also removed an accidental `print(normalized_ids)` debug statement left in the serializer. - Eliminated repeated DB queries in the `ts_proxy` hot path. `StreamManager`, `StreamGenerator`, and `ProxyServer` were each calling `Channel.objects.get(uuid=...)` on every retry, reconnect, failover, and buffering event solely to retrieve `channel.name` for log events. `StreamManager` and `StreamGenerator` now fetch the channel name once at construction via a lightweight `values_list` query and store it as `self.channel_name`. `ProxyServer` caches the name in a `_channel_names` dict keyed by channel ID at channel-start time and pops it at channel-stop time. (Fixes #1138) ### Changed diff --git a/frontend/src/api.js b/frontend/src/api.js index 79d6c435..aa15864b 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -637,6 +637,35 @@ export default class API { } } + /** + * Lightweight stream reorder: PATCHes only the stream order for a channel + * without triggering requeryStreams or requeryChannels. The caller is + * responsible for optimistic UI updates. + */ + static async reorderChannelStreams(channelId, streamIds) { + try { + await request(`${host}/api/channels/channels/${channelId}/`, { + method: 'PATCH', + body: { id: channelId, streams: streamIds }, + }); + // Update the channelsTable store in-place with the new stream order + const store = useChannelsTableStore.getState(); + const channel = store.channels.find((c) => c.id === channelId); + if (channel) { + // Reorder the existing stream objects to match streamIds + const streamMap = new Map(channel.streams.map((s) => [s.id, s])); + const reorderedStreams = streamIds + .map((id) => streamMap.get(id)) + .filter(Boolean); + store.updateChannel({ ...channel, streams: reorderedStreams }); + } + } catch (e) { + errorNotification('Failed to reorder streams', e); + // On failure, requery to restore correct state + await API.requeryChannels(); + } + } + static async updateChannels(ids, values) { const body = []; for (const id of ids) { diff --git a/frontend/src/components/tables/ChannelTableStreams.jsx b/frontend/src/components/tables/ChannelTableStreams.jsx index 4f67dd18..b4ef53c3 100644 --- a/frontend/src/components/tables/ChannelTableStreams.jsx +++ b/frontend/src/components/tables/ChannelTableStreams.jsx @@ -643,13 +643,10 @@ const ChannelStreams = ({ channel }) => { const newIndex = currentIds.indexOf(over.id); const retval = arrayMove(prevData, oldIndex, newIndex); - const { streams: _, ...channelUpdate } = channel; - API.updateChannel({ - ...channelUpdate, - streams: retval.map((row) => row.id), - }).then(() => { - API.requeryChannels(); - }); + API.reorderChannelStreams( + channel.id, + retval.map((row) => row.id) + ); return retval; });