DEV: Do not skip pages when loading polls (#13649)

DEV: Do not skip pages when loading polls (#13649)

In some conditions, pages were skipped. This was implemented in the past in f490a8d, but then reverted in 04ec543, because sometimes it was stuck reloading the first page.

The code that loads more results was simplified and a lot of duplicate code was removed. The logic to remove users who changed their vote was also introduced again, but just for the regular polls.

diff --git a/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6 b/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
index 7ccdbd0..d84407d 100644
--- a/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
+++ b/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
@@ -32,16 +32,6 @@ function infoTextHtml(text) {
   });
 }
 
-function _fetchVoters(data) {
-  return ajax("/polls/voters.json", { data }).catch((error) => {
-    if (error) {
-      popupAjaxError(error);
-    } else {
-      bootbox.alert(I18n.t("poll.error_while_fetching_voters"));
-    }
-  });
-}
-
 function checkUserGroups(user, poll) {
   const pollGroups =
     poll && poll.groups && poll.groups.split(",").map((g) => g.toLowerCase());
@@ -125,14 +115,6 @@ createWidget("discourse-poll-voters", {
   tagName: "ul.poll-voters-list",
   buildKey: (attrs) => `poll-voters-${attrs.optionId}`,
 
-  defaultState() {
-    return {
-      loaded: "new",
-      voters: [],
-      page: 1,
-    };
-  },
-
   html(attrs) {
     const contents = attrs.voters.map((user) =>
       h("li", [
@@ -156,50 +138,13 @@ createWidget("discourse-poll-standard-results", {
   tagName: "ul.results",
   buildKey: (attrs) => `poll-standard-results-${attrs.id}`,
 
-  defaultState() {
-    return { loaded: false };
-  },
-
-  fetchVoters(optionId) {
-    const { attrs, state } = this;
-
-    if (!state.page) {
-      state.page = {};
-    }
-
-    if (!state.page[optionId]) {
-      state.page[optionId] = 1;
-    }
-
-    return _fetchVoters({
-      post_id: attrs.post.id,
-      poll_name: attrs.poll.name,
-      option_id: optionId,
-      page: state.page[optionId] + 1,
-      limit: FETCH_VOTERS_COUNT,
-    }).then((result) => {
-      if (!state.voters[optionId]) {
-        state.voters[optionId] = [];
-      }
-
-      if (result.voters[optionId] && result.voters[optionId].length > 0) {
-        state.voters[optionId] = [
-          ...new Set([...state.voters[optionId], ...result.voters[optionId]]),
-        ];
-        state.page[optionId]++;
-      }
-
-      this.scheduleRerender();
-    });
-  },
-
-  html(attrs, state) {
+  html(attrs) {
     const { poll } = attrs;
-    const options = poll.get("options");
+    const options = poll.options;
 
     if (options) {
-      const voters = poll.get("voters");
-      const isPublic = poll.get("public");
+      const voters = poll.voters;
+      const isPublic = poll.public;
 
       const ordered = [...options].sort((a, b) => {
         if (a.votes < b.votes) {
@@ -215,11 +160,6 @@ createWidget("discourse-poll-standard-results", {
         }
       });
 
-      if (isPublic && !state.loaded) {
-        state.voters = poll.get("preloaded_voters");
-        state.loaded = true;
-      }
-
       const percentages =
         voters === 0
           ? Array(ordered.length).fill(0)
@@ -253,9 +193,9 @@ createWidget("discourse-poll-standard-results", {
             this.attach("discourse-poll-voters", {
               postId: attrs.post.id,
               optionId: option.id,
-              pollName: poll.get("name"),
+              pollName: poll.name,
               totalVotes: option.votes,
-              voters: (state.voters && state.voters[option.id]) || [],
+              voters: (attrs.voters && attrs.voters[option.id]) || [],
             })
           );
         }
@@ -269,45 +209,14 @@ createWidget("discourse-poll-standard-results", {
 createWidget("discourse-poll-number-results", {
   buildKey: (attrs) => `poll-number-results-${attrs.id}`,
 
-  defaultState() {
-    return { loaded: false };
-  },
-
-  fetchVoters(optionId) {
-    const { attrs, state } = this;
-
-    if (!state.page) {
-      state.page = 1;
-    }
-
-    return _fetchVoters({
-      post_id: attrs.post.id,
-      poll_name: attrs.poll.name,
-      option_id: optionId,
-      page: state.page + 1,
-      limit: FETCH_VOTERS_COUNT,
-    }).then((result) => {
-      if (!state.voters) {
-        state.voters = [];
-      }
-
-      if (result.voters && result.voters.length > 0) {
-        state.voters = [...new Set([...state.voters, ...result.voters])];
-        state.page++;
-      }
-
-      this.scheduleRerender();
-    });
-  },
-
-  html(attrs, state) {
+  html(attrs) {
     const { poll } = attrs;
 
-    const totalScore = poll.get("options").reduce((total, o) => {
+    const totalScore = poll.options.reduce((total, o) => {
       return total + parseInt(o.html, 10) * parseInt(o.votes, 10);
     }, 0);
 
-    const voters = poll.get("voters");
+    const voters = poll.voters;
     const average = voters === 0 ? 0 : round(totalScore / voters, -2);
     const averageRating = I18n.t("poll.average_rating", { average });
     const contents = [
@@ -317,19 +226,14 @@ createWidget("discourse-poll-number-results", {
       ),
     ];
 
-    if (poll.get("public")) {
-      if (!state.loaded) {
-        state.voters = poll.get("preloaded_voters");
-        state.loaded = true;
-      }
-
+    if (poll.public) {
       contents.push(
         this.attach("discourse-poll-voters", {
-          totalVotes: poll.get("voters"),
-          voters: state.voters || [],
+          totalVotes: poll.voters,
+          voters: attrs.voters || [],
           postId: attrs.post.id,
-          pollName: poll.get("name"),
-          pollType: poll.get("type"),
+          pollName: poll.name,
+          pollType: poll.type,
         })
       );
     }
@@ -340,10 +244,15 @@ createWidget("discourse-poll-number-results", {
 
 createWidget("discourse-poll-container", {
   tagName: "div.poll-container",
+  buildKey: (attrs) => `poll-container-${attrs.id}`,
 
-  html(attrs) {
+  defaultState() {
+    return { voters: [] };
+  },
+
+  html(attrs, state) {
     const { poll } = attrs;
-    const options = poll.get("options");
+    const options = poll.options;
 
     if (attrs.showResults) {
       const contents = [];
@@ -352,12 +261,21 @@ createWidget("discourse-poll-container", {
         contents.push(new RawHtml({ html: attrs.titleHTML }));
       }
 
-      const type = poll.get("type") === "number" ? "number" : "standard";
+      if (poll.public) {
+        state.voters = poll.preloaded_voters;
+      }
+
+      const type = poll.type === "number" ? "number" : "standard";
       const resultsWidget =
         type === "number" || attrs.poll.chart_type !== PIE_CHART_TYPE
           ? `discourse-poll-${type}-results`
           : "discourse-poll-pie-chart";
-      contents.push(this.attach(resultsWidget, attrs));
+      contents.push(
+        this.attach(
+          resultsWidget,
+          Object.assign({}, attrs, { voters: state.voters })
+        )
+      );
 
       return contents;
     } else if (options) {
@@ -392,6 +310,71 @@ createWidget("discourse-poll-container", {
       return contents;
     }
   },
+
+  fetchVoters(optionId) {
+    const { attrs, state } = this;
+    let votersCount;
+
+    if (optionId) {
+      if (!state.voters) {
+        state.voters = {};
+      }
+
+      if (!state.voters[optionId]) {
+        state.voters[optionId] = [];
+      }
+
+      votersCount = state.voters[optionId].length;
+    } else {
+      if (!state.voters) {
+        state.voters = [];
+      }
+
+      votersCount = state.voters.length;
+    }
+
+    return ajax("/polls/voters.json", {
+      data: {
+        post_id: attrs.post.id,
+        poll_name: attrs.poll.name,
+        option_id: optionId,
+        page: Math.floor(votersCount / FETCH_VOTERS_COUNT) + 1,
+        limit: FETCH_VOTERS_COUNT,
+      },
+    })
+      .then((result) => {
+        const voters = optionId ? state.voters[optionId] : state.voters;
+        const newVoters = optionId ? result.voters[optionId] : result.voters;
+
+        const votersSet = new Set(voters.map((voter) => voter.username));
+        newVoters.forEach((voter) => {
+          if (!votersSet.has(voter.username)) {
+            votersSet.add(voter.username);
+            voters.push(voter);
+          }
+        });
+

[... diff too long, it was truncated ...]

GitHub sha: 573a71fdd9089c245678d57bc9232f2839e16927

This commit appears in #13649 which was approved by ZogStriP. It was merged by nbianca.