From 80cf913d05f472e56fc27f300ad984e1a7c59f57 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Thu, 27 Apr 2023 12:51:13 +0400 Subject: [PATCH 1/9] feat: Use sidekick server to handle CSV report download --- .env | 1 + src/components/SpaceProposalVotesList.vue | 41 +++++-- src/composables/useReportDownload.ts | 131 ++++++---------------- src/locales/default.json | 12 +- src/locales/fr-FR.json | 12 +- 5 files changed, 87 insertions(+), 110 deletions(-) diff --git a/.env b/.env index 46dcccea7a61..572c8299e5c5 100644 --- a/.env +++ b/.env @@ -1,6 +1,7 @@ VITE_HUB_URL=https://testnet.snapshot.org VITE_RELAYER_URL=https://testnet.snapshot.org VITE_SCORES_URL=https://score.snapshot.org +VITE_SIDEKICK_URL=https://sidekick.snapshot.org VITE_IPFS_GATEWAY=snapshot.mypinata.cloud VITE_DEFAULT_NETWORK=1 VITE_PUSHER_BEAMS_INSTANCE_ID=2e080021-d495-456d-b2cf-84f9fd718442 diff --git a/src/components/SpaceProposalVotesList.vue b/src/components/SpaceProposalVotesList.vue index 3665b814f803..bd893c9ab60c 100644 --- a/src/components/SpaceProposalVotesList.vue +++ b/src/components/SpaceProposalVotesList.vue @@ -20,8 +20,16 @@ const modalVotesmOpen = ref(false); const voteCount = computed(() => props.proposal.votes); -const { downloadVotes, isDownloadingVotes, downloadProgress } = - useReportDownload(); +const modalVotesDownloadOpen = ref(false); +const { downloadVotes, isDownloadingVotes, errorCode } = useReportDownload(); + +async function downloadReport(proposalId: string) { + const response = await downloadVotes(proposalId); + + if (!response) { + modalVotesDownloadOpen.value = true; + } +} onMounted(async () => { await loadVotes(); @@ -38,19 +46,28 @@ onMounted(async () => { > , - headers: string[], - fileName: string - ) { - const csv = await jsonexport(data, { headers }); - const link = document.createElement('a'); - link.setAttribute('href', `data:text/csv;charset=utf-8,${csv}`); - link.setAttribute('download', `${fileName}.csv`); - document.body.appendChild(link); - link.click(); - } - - async function getAllVotes( - proposalId: string, - space: string, - totalVotesCount: number - ) { - let votes: Vote[] = []; - let page = 0; - let createdPivot = 0; - const pageSize = 1000; - let resultsSize = 0; - const maxPage = 5; - do { - let newVotes = await getProposalVotes(proposalId, { - first: pageSize, - skip: page * pageSize, - space: space, - created_gte: createdPivot, - orderBy: 'created', - orderDirection: 'asc' - }); - resultsSize = newVotes.length; - - if (page === 0 && createdPivot > 0) { - const existingIpfs = votes.slice(-pageSize).map(vote => vote.ipfs); - - newVotes = newVotes.filter(vote => { - return !existingIpfs.includes(vote.ipfs); - }); - } - - if (page === maxPage) { - page = 0; - createdPivot = newVotes[newVotes.length - 1].created; - } else { - page++; - } - - votes = [...votes, ...newVotes]; - downloadProgress.value = Math.floor( - (votes.length / totalVotesCount) * 100 - ); - } while (resultsSize === pageSize); - return votes; + const errorCode: globalThis.Ref = ref(null); + + async function downloadFile(blob: Blob, fileName: string) { + const href = URL.createObjectURL(blob); + const a = Object.assign(document.createElement('a'), { + href, + style: 'display:none', + download: fileName + }); + document.body.appendChild(a); + a.click(); + URL.revokeObjectURL(href); + a.remove(); } - async function downloadVotes(proposalId: string, space: string) { + async function downloadVotes(proposalId: string) { isDownloadingVotes.value = true; - const proposal = await getProposal(proposalId); - const votes = await getAllVotes(proposalId, space, proposal.votes); - if (!votes.length) return; - const data = votes.map(vote => { - return { - address: vote.voter, - choice: vote.choice, - voting_power: vote.vp, - timestamp: vote.created, - date_utc: new Date(vote.created * 1e3).toUTCString(), - author_ipfs_hash: vote.ipfs - }; - }); - try { - getCsvFile( - data, - [ - 'address', - ...proposal.choices.map((choice, index) => `choice.${index + 1}`), - 'voting_power', - 'timestamp', - 'date_utc', - 'author_ipfs_hash' - ], - `${pkg.name}-report-${proposalId}` - ); - } catch (e) { - console.error(e); - isDownloadingVotes.value = false; - } - isDownloadingVotes.value = false; + errorCode.value = null; + + return fetch(`${import.meta.env.VITE_SIDEKICK_URL}/votes/${proposalId}`, { + method: 'POST' + }) + .then(async response => { + if (response.status !== 200) { + throw new Error((await response.json()).error.message); + } + return response.blob(); + }) + .then(blob => { + downloadFile(blob, `${pkg.name}-report-${proposalId}`); + return true; + }) + .catch((e: Error) => { + errorCode.value = e; + return false; + }) + .finally(() => { + isDownloadingVotes.value = false; + }); } return { downloadVotes, isDownloadingVotes, - downloadProgress + errorCode }; } diff --git a/src/locales/default.json b/src/locales/default.json index 1531f2066a08..92ad3a7bd63a 100644 --- a/src/locales/default.json +++ b/src/locales/default.json @@ -258,8 +258,16 @@ "1": "Votes can be changed while the proposal is active" } }, - "downloadCsvVotes": "Download as CSV", - "preparingCsvVotes": "Preparing file" + "downloadCsvVotes": { + "title": "Download as CSV", + "postDownloadModal": { + "title": "Download votes report", + "message": { + "pendingGeneration": "The report is generating… Please try again in a few minutes.", + "unknownError": "Unable to contact the download server. Please try again later." + } + } + } }, "proposals": { "header": "Proposals", diff --git a/src/locales/fr-FR.json b/src/locales/fr-FR.json index 8a8ac1efbe86..aff698dcee70 100644 --- a/src/locales/fr-FR.json +++ b/src/locales/fr-FR.json @@ -255,8 +255,16 @@ "1": "Les votes peuvent être modifiés tant que la proposition est active" } }, - "downloadCsvVotes": "Télécharger en tant que CSV", - "preparingCsvVotes": "Préparation du fichier" + "downloadCsvVotes": { + "title": "Télécharger en tant que CSV", + "postDownloadModal": { + "title": "Téléchargement du rapport", + "message": { + "pendingGeneration": "Le rapport est en cours de génération… Veuillez réessayer dans quelques minutes.", + "unknownError": "Échec de la connexion au serveur de téléchargement. Veuillez réessayer plus tard." + } + } + } }, "proposals": { "header": "Propositions", From 2be5eccbc8580f83e415857be3aa9faadb6eeaf0 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Sun, 14 May 2023 01:19:06 +0400 Subject: [PATCH 2/9] fix: update sidekick API url --- .env | 2 +- src/composables/useReportDownload.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.env b/.env index 572c8299e5c5..b1ae5a78a4a6 100644 --- a/.env +++ b/.env @@ -1,7 +1,7 @@ VITE_HUB_URL=https://testnet.snapshot.org VITE_RELAYER_URL=https://testnet.snapshot.org VITE_SCORES_URL=https://score.snapshot.org -VITE_SIDEKICK_URL=https://sidekick.snapshot.org +VITE_SIDEKICK_URL=https://sh5.co VITE_IPFS_GATEWAY=snapshot.mypinata.cloud VITE_DEFAULT_NETWORK=1 VITE_PUSHER_BEAMS_INSTANCE_ID=2e080021-d495-456d-b2cf-84f9fd718442 diff --git a/src/composables/useReportDownload.ts b/src/composables/useReportDownload.ts index eeb058ff1468..90ce2c3cb6d4 100644 --- a/src/composables/useReportDownload.ts +++ b/src/composables/useReportDownload.ts @@ -21,9 +21,12 @@ export function useReportDownload() { isDownloadingVotes.value = true; errorCode.value = null; - return fetch(`${import.meta.env.VITE_SIDEKICK_URL}/votes/${proposalId}`, { - method: 'POST' - }) + return fetch( + `${import.meta.env.VITE_SIDEKICK_URL}/api/votes/${proposalId}`, + { + method: 'POST' + } + ) .then(async response => { if (response.status !== 200) { throw new Error((await response.json()).error.message); From c8db05796aa12519161e1dcba2ab21a86cf880ea Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Fri, 26 May 2023 21:30:28 +0400 Subject: [PATCH 3/9] fix: remove translation, should be updated via third-party service --- src/locales/fr-FR.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/locales/fr-FR.json b/src/locales/fr-FR.json index aff698dcee70..1da69fa82792 100644 --- a/src/locales/fr-FR.json +++ b/src/locales/fr-FR.json @@ -254,16 +254,6 @@ "tips": { "1": "Les votes peuvent être modifiés tant que la proposition est active" } - }, - "downloadCsvVotes": { - "title": "Télécharger en tant que CSV", - "postDownloadModal": { - "title": "Téléchargement du rapport", - "message": { - "pendingGeneration": "Le rapport est en cours de génération… Veuillez réessayer dans quelques minutes.", - "unknownError": "Échec de la connexion au serveur de téléchargement. Veuillez réessayer plus tard." - } - } } }, "proposals": { From d6d28fb783ec190cb833ca11e1c8e1f72cf8b848 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Fri, 26 May 2023 21:39:45 +0400 Subject: [PATCH 4/9] fix: restore old translations --- src/locales/fr-FR.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/locales/fr-FR.json b/src/locales/fr-FR.json index 1da69fa82792..8a8ac1efbe86 100644 --- a/src/locales/fr-FR.json +++ b/src/locales/fr-FR.json @@ -254,7 +254,9 @@ "tips": { "1": "Les votes peuvent être modifiés tant que la proposition est active" } - } + }, + "downloadCsvVotes": "Télécharger en tant que CSV", + "preparingCsvVotes": "Préparation du fichier" }, "proposals": { "header": "Propositions", From 98e870546f1badd154b808593b90f5e4a5a9633f Mon Sep 17 00:00:00 2001 From: Sam <51686767+samuveth@users.noreply.github.com> Date: Sat, 27 May 2023 10:04:14 +0700 Subject: [PATCH 5/9] Fix naming --- src/components/SpaceProposalVotesList.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/SpaceProposalVotesList.vue b/src/components/SpaceProposalVotesList.vue index bd893c9ab60c..e3ead1f13ef3 100644 --- a/src/components/SpaceProposalVotesList.vue +++ b/src/components/SpaceProposalVotesList.vue @@ -20,14 +20,14 @@ const modalVotesmOpen = ref(false); const voteCount = computed(() => props.proposal.votes); -const modalVotesDownloadOpen = ref(false); +const showModalDownloadMessage = ref(false); const { downloadVotes, isDownloadingVotes, errorCode } = useReportDownload(); async function downloadReport(proposalId: string) { const response = await downloadVotes(proposalId); if (!response) { - modalVotesDownloadOpen.value = true; + showModalDownloadMessage.value = true; } } @@ -53,7 +53,7 @@ onMounted(async () => { From e4b2656877ae30a516c87f185fa599af747ebbf7 Mon Sep 17 00:00:00 2001 From: Wan <495709+wa0x6e@users.noreply.github.com> Date: Sun, 28 May 2023 19:36:35 +0400 Subject: [PATCH 6/9] Update src/locales/default.json Co-authored-by: Sam <51686767+samuveth@users.noreply.github.com> --- src/locales/default.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/default.json b/src/locales/default.json index 02a3b3d97e94..ac6da853d192 100644 --- a/src/locales/default.json +++ b/src/locales/default.json @@ -263,8 +263,8 @@ "postDownloadModal": { "title": "Download votes report", "message": { - "pendingGeneration": "The report is generating… Please try again in a few minutes.", - "unknownError": "Unable to contact the download server. Please try again later." + "pendingGeneration": "Your report is currently being generated. It may take a few minutes. Please check back shortly.", + "unknownError": "We're having trouble connecting to the server responsible for downloads. Please try again in a few moments. If the problem persists, consider contacting our support team." } } } From 4de013eba91b45a5e882c5df39144632b57eec01 Mon Sep 17 00:00:00 2001 From: Wan <495709+wa0x6e@users.noreply.github.com> Date: Sun, 28 May 2023 19:36:49 +0400 Subject: [PATCH 7/9] Update src/locales/default.json Co-authored-by: Sam <51686767+samuveth@users.noreply.github.com> --- src/locales/default.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/default.json b/src/locales/default.json index ac6da853d192..b8419e0234fb 100644 --- a/src/locales/default.json +++ b/src/locales/default.json @@ -261,7 +261,7 @@ "downloadCsvVotes": { "title": "Download as CSV", "postDownloadModal": { - "title": "Download votes report", + "title": "Generating votes report", "message": { "pendingGeneration": "Your report is currently being generated. It may take a few minutes. Please check back shortly.", "unknownError": "We're having trouble connecting to the server responsible for downloads. Please try again in a few moments. If the problem persists, consider contacting our support team." From 55abff153c25ea6b49502ede7df292cb524585fb Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Thu, 8 Jun 2023 02:11:09 +0400 Subject: [PATCH 8/9] fix: use `async`/`await` syntax --- src/composables/useReportDownload.ts | 41 +++++++++++++--------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/composables/useReportDownload.ts b/src/composables/useReportDownload.ts index 90ce2c3cb6d4..7f82d3fcd88f 100644 --- a/src/composables/useReportDownload.ts +++ b/src/composables/useReportDownload.ts @@ -21,29 +21,26 @@ export function useReportDownload() { isDownloadingVotes.value = true; errorCode.value = null; - return fetch( - `${import.meta.env.VITE_SIDEKICK_URL}/api/votes/${proposalId}`, - { - method: 'POST' - } - ) - .then(async response => { - if (response.status !== 200) { - throw new Error((await response.json()).error.message); + try { + const response = await fetch( + `${import.meta.env.VITE_SIDEKICK_URL}/api/votes/${proposalId}`, + { + method: 'POST' } - return response.blob(); - }) - .then(blob => { - downloadFile(blob, `${pkg.name}-report-${proposalId}`); - return true; - }) - .catch((e: Error) => { - errorCode.value = e; - return false; - }) - .finally(() => { - isDownloadingVotes.value = false; - }); + ); + + if (response.status !== 200) { + throw new Error((await response.json()).error.message); + } + + downloadFile(await response.blob(), `${pkg.name}-report-${proposalId}`); + return true; + } catch (e: any) { + errorCode.value = e; + return false; + } finally { + isDownloadingVotes.value = false; + } } return { From 93c769a92107a3211983a6a4d245c2b3fc4e1d43 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Thu, 8 Jun 2023 02:37:49 +0400 Subject: [PATCH 9/9] fix(UI): improve modal UI --- src/components/SpaceProposalVotesList.vue | 57 +++++++++++++++++------ src/locales/default.json | 10 +++- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/src/components/SpaceProposalVotesList.vue b/src/components/SpaceProposalVotesList.vue index e3ead1f13ef3..d912d3ed497a 100644 --- a/src/components/SpaceProposalVotesList.vue +++ b/src/components/SpaceProposalVotesList.vue @@ -31,6 +31,14 @@ async function downloadReport(proposalId: string) { } } +const errorMessagekeyPrefix = computed(() => { + return `proposal.downloadCsvVotes.postDownloadModal.message.${ + errorCode?.value?.message === 'PENDING_GENERATION' + ? 'pendingGeneration' + : 'unknownError' + }`; +}); + onMounted(async () => { await loadVotes(); }); @@ -52,22 +60,45 @@ onMounted(async () => { > - - + + +
+ + +

+ {{ $t(`${errorMessagekeyPrefix}.title`) }} +

+

+ {{ $t(`${errorMessagekeyPrefix}.description`) }} +

+
+ + +