Skip to content

Commit eea7885

Browse files
author
Kimmo Virtanen
committed
do not load poll info on every vote
1 parent 58fa31c commit eea7885

2 files changed

Lines changed: 139 additions & 1 deletion

File tree

polls/static/polls/js/app.js

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1389,6 +1389,7 @@ const translations = {
13891389
},
13901390
showAuthDialog: false,
13911391
pendingAction: null,
1392+
pollListNeedsRefresh: false,
13921393
errorMessage: "",
13931394
successMessage: "",
13941395
successFeedbackTimerId: null,
@@ -2577,6 +2578,9 @@ const translations = {
25772578
this.focusPollFormInitialField("create");
25782579
}
25792580
});
2581+
if (section === "list") {
2582+
void this.refreshPollListOnReturnIfNeeded();
2583+
}
25802584
},
25812585
resetFormValidation(scope = "create") {
25822586
this.formErrors[scope] = {};
@@ -3793,6 +3797,65 @@ const translations = {
37933797
}
37943798
this.voteDraft = nextDraft;
37953799
},
3800+
pollSummaryFromDetail(poll) {
3801+
if (!poll || typeof poll !== "object") {
3802+
return null;
3803+
}
3804+
const options = Array.isArray(poll.options) ? poll.options : [];
3805+
let myVoteCount = 0;
3806+
for (const option of options) {
3807+
if (this.normalizeVoteValue(option && option.my_vote)) {
3808+
myVoteCount += 1;
3809+
}
3810+
}
3811+
return {
3812+
id: poll.id,
3813+
identifier: typeof poll.identifier === "string" ? poll.identifier : "",
3814+
title: typeof poll.title === "string" ? poll.title : "",
3815+
description: typeof poll.description === "string" ? poll.description : "",
3816+
window_starts_at: poll.window_starts_at || "",
3817+
window_ends_at: poll.window_ends_at || "",
3818+
start_date: typeof poll.start_date === "string" ? poll.start_date : "",
3819+
end_date: typeof poll.end_date === "string" ? poll.end_date : "",
3820+
slot_minutes: Number.isInteger(poll.slot_minutes) ? poll.slot_minutes : 60,
3821+
daily_start_hour: Number.isInteger(poll.daily_start_hour) ? poll.daily_start_hour : 0,
3822+
daily_end_hour: Number.isInteger(poll.daily_end_hour) ? poll.daily_end_hour : 24,
3823+
allowed_weekdays: Array.isArray(poll.allowed_weekdays) ? [...poll.allowed_weekdays] : [],
3824+
timezone: typeof poll.timezone === "string" ? poll.timezone : "",
3825+
is_closed: Boolean(poll.is_closed),
3826+
created_at: poll.created_at || "",
3827+
closed_at: poll.closed_at || null,
3828+
creator: poll.creator || null,
3829+
option_count: options.length,
3830+
participant_count: Number.isInteger(poll.participant_count) ? poll.participant_count : 0,
3831+
my_vote_count: myVoteCount,
3832+
can_close: Boolean(poll.can_close),
3833+
can_reopen: Boolean(poll.can_reopen),
3834+
can_delete: Boolean(poll.can_delete),
3835+
can_edit: Boolean(poll.can_edit)
3836+
};
3837+
},
3838+
patchPollSummaryFromDetail(poll) {
3839+
const summary = this.pollSummaryFromDetail(poll);
3840+
if (!summary) {
3841+
return;
3842+
}
3843+
const pollId = String(summary.id || "");
3844+
const existingIndex = this.polls.findIndex((item) => String(item && item.id || "") === pollId);
3845+
if (existingIndex >= 0) {
3846+
const nextPolls = [...this.polls];
3847+
nextPolls.splice(existingIndex, 1, summary);
3848+
this.polls = nextPolls;
3849+
return;
3850+
}
3851+
this.polls = [...this.polls, summary];
3852+
},
3853+
async refreshPollListOnReturnIfNeeded() {
3854+
if (this.activeSection !== "list" || !this.pollListNeedsRefresh) {
3855+
return;
3856+
}
3857+
await this.fetchPolls();
3858+
},
37963859
async waitForPendingVoteSync() {
37973860
if (!this.selectedPoll || this.selectedPoll.is_closed) {
37983861
return;
@@ -3853,7 +3916,8 @@ const translations = {
38533916
this.selectedPoll = data.poll;
38543917
this.applyVoteDraft({ preserveLocalChanges: true });
38553918
}
3856-
await this.fetchPolls();
3919+
this.patchPollSummaryFromDetail(data.poll);
3920+
this.pollListNeedsRefresh = true;
38573921
} catch (error) {
38583922
if (
38593923
generation === Number(this._voteSyncGeneration || 0)
@@ -4506,6 +4570,7 @@ const translations = {
45064570
try {
45074571
const data = await apiFetch("/api/polls/");
45084572
this.polls = data.polls || [];
4573+
this.pollListNeedsRefresh = false;
45094574
} catch (error) {
45104575
this.setError(this.resolveError(error.payload, "Could not load polls."));
45114576
}

polls/tests_browser.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2788,6 +2788,79 @@ def test_browser_vote_request_recovers_from_csrf_retry(self) -> None:
27882788
self.assertEqual(first_yes_button.get_attribute("aria-checked"), "true")
27892789
self.assertEqual(page.locator(".feedback.error").count(), 0)
27902790

2791+
def test_browser_vote_sync_updates_local_poll_summary_and_defers_poll_list_refresh_until_home(self) -> None:
2792+
page = self.require_page()
2793+
2794+
self.open_home_page()
2795+
self.login(name="poll-list-refresh-owner")
2796+
self.create_poll(
2797+
title="Poll list refresh vote poll",
2798+
description="Used to verify vote sync avoids eager poll list refetches.",
2799+
identifier="poll_list_refresh_vote_poll",
2800+
timezone="Europe/Helsinki",
2801+
start_date="2026-04-13",
2802+
end_date="2026-04-13",
2803+
)
2804+
2805+
page.evaluate(
2806+
"""
2807+
() => {
2808+
const originalFetch = window.fetch.bind(window);
2809+
const voteUrlPattern = /\\/api\\/polls\\/[^/]+\\/votes\\/$/;
2810+
const pollsListUrlPattern = /\\/api\\/polls\\/$/;
2811+
window.__timepollPollListRefreshStats = {
2812+
listFetchCount: 0,
2813+
votePutCount: 0
2814+
};
2815+
window.fetch = async (input, init = {}) => {
2816+
const requestUrl = typeof input === 'string' ? input : input.url;
2817+
const requestMethod = String(
2818+
(init && init.method)
2819+
|| (typeof input === 'object' && input && input.method)
2820+
|| 'GET'
2821+
).toUpperCase();
2822+
if (requestMethod === 'PUT' && voteUrlPattern.test(requestUrl)) {
2823+
window.__timepollPollListRefreshStats.votePutCount += 1;
2824+
}
2825+
if (requestMethod === 'GET' && pollsListUrlPattern.test(requestUrl)) {
2826+
window.__timepollPollListRefreshStats.listFetchCount += 1;
2827+
}
2828+
return originalFetch(input, init);
2829+
};
2830+
}
2831+
"""
2832+
)
2833+
2834+
first_yes_button = page.locator(".vote-switch-option-yes").first
2835+
first_yes_button.click()
2836+
self.wait_for_first_vote_state(".vote-switch-option-yes", True, page=page)
2837+
page.wait_for_function(
2838+
"""
2839+
() => {
2840+
const stats = window.__timepollPollListRefreshStats;
2841+
return Boolean(stats) && stats.votePutCount >= 1;
2842+
}
2843+
"""
2844+
)
2845+
page.wait_for_timeout(300)
2846+
2847+
stats_before_home = page.evaluate("() => window.__timepollPollListRefreshStats")
2848+
self.assertEqual(stats_before_home["listFetchCount"], 0, stats_before_home)
2849+
2850+
page.locator(".title-home").click()
2851+
page.get_by_role("heading", name="Polls").wait_for()
2852+
page.wait_for_function(
2853+
"""
2854+
() => {
2855+
const stats = window.__timepollPollListRefreshStats;
2856+
return Boolean(stats) && stats.listFetchCount >= 1;
2857+
}
2858+
"""
2859+
)
2860+
2861+
stats_after_home = page.evaluate("() => window.__timepollPollListRefreshStats")
2862+
self.assertGreaterEqual(stats_after_home["listFetchCount"], 1, stats_after_home)
2863+
27912864
def test_browser_bulk_vote_by_day_and_time_row(self) -> None:
27922865
page = self.require_page()
27932866

0 commit comments

Comments
 (0)