refactor(stats): simplify channel logo handling in stats data

This update refactors the logic for building timeshift statistics by removing unnecessary `select_related` calls and the `logo_url` field from the connections. The changes streamline the data retrieval process and ensure that only the `logo_id` is exposed in the session data. Corresponding tests have been updated to reflect these modifications.
This commit is contained in:
SergeantPanda 2026-07-17 23:21:44 +00:00
parent b24f39d9f8
commit 0aed30253b
4 changed files with 102 additions and 13 deletions

View file

@ -490,9 +490,7 @@ def build_timeshift_stats_data(redis_client):
channel_ids = {conn["channel_id"] for conn in connections if conn.get("channel_id")}
channels_by_id = {}
if channel_ids:
for channel in Channel.objects.select_related("logo", "epg_data").filter(
id__in=channel_ids,
):
for channel in Channel.objects.filter(id__in=channel_ids):
channels_by_id[channel.id] = channel
profile_ids = {
@ -516,8 +514,6 @@ def build_timeshift_stats_data(redis_client):
conn["channel_name"] = channel.name
if channel.logo_id:
conn["logo_id"] = channel.logo_id
if channel.logo:
conn["logo_url"] = channel.logo.url
profile = profiles_by_id.get(conn.get("m3u_profile_id"))
if profile:
@ -546,7 +542,6 @@ def build_timeshift_stats_data(redis_client):
"channel_uuid": conn.get("channel_uuid", ""),
"channel_name": conn["channel_name"],
"logo_id": conn.get("logo_id"),
"logo_url": conn.get("logo_url"),
"programme_start": conn["programme_start"],
"position_anchor_at": position_anchor_at,
"playback_base_secs": playback_base_secs,

View file

@ -300,7 +300,7 @@ class BuildTimeshiftStatsDataTests(TestCase):
channel.uuid = "00000000-0000-0000-0000-000000000042"
channel.logo_id = None
channel.logo = None
mock_channel_model.objects.select_related.return_value.filter.return_value = [
mock_channel_model.objects.filter.return_value = [
channel,
]
payload = build_timeshift_stats_data(self.redis)
@ -317,6 +317,18 @@ class BuildTimeshiftStatsDataTests(TestCase):
self.assertFalse(session["paused"])
self.assertEqual(session["connections"][0]["ip_address"], "10.0.0.5")
@patch("apps.timeshift.stats.Channel")
def test_build_timeshift_stats_exposes_logo_id_only(self, mock_channel_model):
channel = MagicMock()
channel.id = self.channel_id
channel.name = "Catch-up Stats Channel"
channel.uuid = "00000000-0000-0000-0000-000000000042"
channel.logo_id = 77
mock_channel_model.objects.filter.return_value = [channel]
session = build_timeshift_stats_data(self.redis)["timeshift_sessions"][0]
self.assertEqual(session["logo_id"], 77)
self.assertNotIn("logo_url", session)
def test_find_stats_channel_for_session(self):
found = find_stats_channel_for_session(self.redis, self.session_id)
self.assertEqual(found, self.stats_channel_id)
@ -344,7 +356,7 @@ class BuildTimeshiftStatsDataTests(TestCase):
channel.uuid = "00000000-0000-0000-0000-000000000042"
channel.logo_id = None
channel.logo = None
mock_channel_model.objects.select_related.return_value.filter.return_value = [
mock_channel_model.objects.filter.return_value = [
channel,
]
client_key = RedisKeys.client_metadata(self.stats_channel_id, self.session_id)
@ -355,7 +367,7 @@ class BuildTimeshiftStatsDataTests(TestCase):
@patch("apps.timeshift.stats.Channel")
def test_skips_clients_missing_required_metadata(self, mock_channel_model):
mock_channel_model.objects.select_related.return_value.filter.return_value = []
mock_channel_model.objects.filter.return_value = []
orphan_key = RedisKeys.client_metadata(self.stats_channel_id, "orphan")
self.redis.hset(orphan_key, mapping={"user_agent": "VLC"})
self.redis.sadd(RedisKeys.clients(self.stats_channel_id), "orphan")

View file

@ -135,10 +135,7 @@ const TimeshiftConnectionCard = ({
timeshiftSession.individual_connection ||
(timeshiftSession.connections && timeshiftSession.connections[0]);
const logoUrl =
timeshiftSession.logo_url ||
getLogoUrl(timeshiftSession.logo_id, logos) ||
logo;
const logoUrl = getLogoUrl(timeshiftSession.logo_id, logos) || logo;
const programmePreview = useMemo(() => {
if (currentProgram?.title) {

View file

@ -0,0 +1,85 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
vi.mock('../../../utils/dateTimeUtils.js', () => ({
useDateTimeFormat: vi.fn(() => ({ fullDateTimeFormat: 'MM/DD/YYYY h:mm A' })),
}));
vi.mock('../../../store/users.jsx', () => ({
default: vi.fn(() => []),
}));
vi.mock('../../../images/logo.png', () => ({ default: 'default-logo.png' }));
vi.mock('../../../utils/cards/StreamConnectionCardUtils.js', () => ({
getLogoUrl: vi.fn(),
}));
vi.mock('../../../utils/cards/TimeshiftConnectionCardUtils.js', () => ({
calculateConnectionDuration: vi.fn(() => '5m'),
calculateConnectionStartTime: vi.fn(() => 'Jan 1 2024 10:00 AM'),
computeCatchupPlaybackSeconds: vi.fn(() => 0),
getConnectionDurationSeconds: vi.fn(() => 0),
}));
vi.mock('@mantine/core', () => ({
ActionIcon: ({ children }) => <button type="button">{children}</button>,
Badge: ({ children }) => <span>{children}</span>,
Box: ({ children }) => <div>{children}</div>,
Card: ({ children }) => <div data-testid="timeshift-connection-card">{children}</div>,
Center: ({ children }) => <div>{children}</div>,
Group: ({ children }) => <div>{children}</div>,
Stack: ({ children }) => <div>{children}</div>,
Text: ({ children }) => <span>{children}</span>,
Tooltip: ({ children }) => <>{children}</>,
}));
vi.mock('lucide-react', () => ({
ChevronDown: () => null,
HardDriveUpload: () => null,
History: () => null,
SquareX: () => null,
Timer: () => null,
}));
vi.mock('../../ProgramPreview.jsx', () => ({
default: () => <div data-testid="program-preview" />,
}));
import TimeshiftConnectionCard from '../TimeshiftConnectionCard.jsx';
import { getLogoUrl } from '../../../utils/cards/StreamConnectionCardUtils.js';
describe('TimeshiftConnectionCard logos', () => {
beforeEach(() => {
vi.clearAllMocks();
getLogoUrl.mockReturnValue('/api/channels/logos/77/cache/');
});
it('resolves the channel logo via logo_id and the logos store', () => {
render(
<TimeshiftConnectionCard
timeshiftSession={{
session_id: 'sess-1',
channel_name: 'US | A&E',
logo_id: 77,
programme_start: '2026-07-17:20-00-00',
connections: [
{
session_id: 'sess-1',
client_id: 'sess-1',
ip_address: '10.0.0.5',
connected_at: 1_700_000_000,
},
],
}}
logos={{ 77: { cache_url: '/api/channels/logos/77/cache/' } }}
/>
);
expect(getLogoUrl).toHaveBeenCalledWith(77, {
77: { cache_url: '/api/channels/logos/77/cache/' },
});
const img = screen.getByAltText('channel logo');
expect(img.getAttribute('src')).toBe('/api/channels/logos/77/cache/');
});
});