From 52f6b46dbaa71acad6953254842473a01220a6a5 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Fri, 1 Aug 2025 12:03:36 +0545 Subject: [PATCH 01/22] chore: add test ids for updated vote rationale components --- govtool/frontend/src/components/molecules/VoteActionForm.tsx | 5 +++-- .../components/organisms/VoteContext/VoteContextChoice.tsx | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/govtool/frontend/src/components/molecules/VoteActionForm.tsx b/govtool/frontend/src/components/molecules/VoteActionForm.tsx index 25c1807d2..6a49ac90b 100644 --- a/govtool/frontend/src/components/molecules/VoteActionForm.tsx +++ b/govtool/frontend/src/components/molecules/VoteActionForm.tsx @@ -275,7 +275,7 @@ export const VoteActionForm = ({ border: !showWholeVoteContext ? "1px solid #E1E1E1" : "none", borderRadius: "4px", backgroundColor: !showWholeVoteContext ? fadedPurple.c50 : "transparent", - padding: 2, + padding: 2 }} > {finalVoteContextText} @@ -316,7 +317,7 @@ export const VoteActionForm = ({ }} disableRipple variant="text" - data-testid="external-modal-button" + data-testid="show-more-button" > {t("createGovernanceAction.govToolPinsDataToIPFS")} @@ -77,6 +78,7 @@ export const VoteContextChoice = ({ variant="outlined" onClick={handleStoreItMyself} sx={{ width: isMobile ? "100%" : "287px", whiteSpace: "nowrap", height: "48px", fontWeight: "500" }} + data-testid="download-and-store-yourself-option-button" > {t("createGovernanceAction.downloadAndStoreYourself")} From 163ec86c7c38698f385a43575e73468241a3ab28 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Fri, 1 Aug 2025 12:05:14 +0545 Subject: [PATCH 02/22] fix: update include metadata anchor in vote transaction test --- .../playwright/lib/helpers/waitedLoop.ts | 5 +++-- .../lib/pages/governanceActionDetailsPage.ts | 22 +++++++++++-------- .../proposalVisibility.dRep.spec.ts | 20 ++++++++++++----- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/helpers/waitedLoop.ts b/tests/govtool-frontend/playwright/lib/helpers/waitedLoop.ts index cbe537a37..45e04ad33 100644 --- a/tests/govtool-frontend/playwright/lib/helpers/waitedLoop.ts +++ b/tests/govtool-frontend/playwright/lib/helpers/waitedLoop.ts @@ -9,7 +9,7 @@ export async function waitedLoop( const startTime = Date.now(); while (Date.now() - startTime < timeout) { if (await conditionFn()) return true; - Logger.info("Retring the function"); + Logger.info("Retrying the function"); await new Promise((resolve) => setTimeout(resolve, interval)); } return false; @@ -36,9 +36,10 @@ export async function functionWaitedAssert( } catch (error) { if (Date.now() - startTime >= timeout) { const errorMessage = options.message || error.message; + console.log(errorMessage); expect(false, { message: errorMessage }).toBe(true); } - Logger.info(`Retring the function ${name}`); + Logger.info(`Retrying the function ${name}`); await new Promise((resolve) => setTimeout(resolve, interval)); } } diff --git a/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts index d6a8713a4..800b0f29c 100644 --- a/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts @@ -31,6 +31,9 @@ export default class GovernanceActionDetailsPage { readonly continueModalBtn = this.page.getByTestId("continue-modal-button"); readonly confirmModalBtn = this.page.getByTestId("confirm-modal-button"); + readonly downloadAndStoreYourselfOptionBtn = this.page.getByTestId("download-and-store-yourself-option-button") + readonly govtoolPinsDatatoIpfsBtn = this.page.getByTestId("govtool-pins-data-to-ipfs-option-button") + readonly voteSuccessModal = this.page.getByTestId("alert-success"); readonly externalLinkModal = this.page.getByTestId("external-link-modal"); @@ -81,10 +84,16 @@ export default class GovernanceActionDetailsPage { await this.yesVoteRadio.click(); } + await this.voteBtn.click() + if (context) { - await this.contextBtn.click(); + // await this.contextBtn.click(); await this.contextInput.fill(context); - await this.confirmModalBtn.click(); + + this.confirmModalBtn.click() + + await this.downloadAndStoreYourselfOptionBtn.click() + await this.page.getByRole("checkbox").click(); await this.confirmModalBtn.click(); @@ -100,13 +109,8 @@ export default class GovernanceActionDetailsPage { await this.page.getByTestId("go-to-vote-modal-button").click(); } - const isVoteButtonEnabled = await this.voteBtn.isEnabled(); - - await expect(this.voteBtn, { - message: !isVoteButtonEnabled && "Vote button is not enabled", - }).toBeEnabled({ timeout: 60_000 }); - - await this.voteBtn.click(); + // const isVoteButtonEnabled = await this.voteBtn.isEnabled(); + // await this.voteBtn.click() } async getDRepNotVoted( diff --git a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts index 7e6623005..469e32a2d 100644 --- a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts @@ -123,15 +123,25 @@ test.describe("Temporary DReps", async () => { await govActionsPage.goto(); const govActionDetailsPage = await govActionsPage.viewFirstProposal(); - await govActionDetailsPage.vote(faker.lorem.sentence(200)); - await dRepPage.waitForTimeout(5_000); + const fakerContext = faker.lorem.sentence(200) + await govActionDetailsPage.vote(fakerContext); + + await dRepPage.reload();1 + await dRepPage.waitForTimeout(5_000);1 await govActionsPage.votedTab.click(); - await govActionsPage.viewFirstVotedProposal(); + + const votedGovActionDetailsPage = await govActionsPage.viewFirstVotedProposal(); + + await votedGovActionDetailsPage.currentPage.getByTestId("show-more-button").click() + + await votedGovActionDetailsPage.currentPage.waitForTimeout(2000) + + const voteRationaleContext = await votedGovActionDetailsPage.currentPage.getByTestId("vote-rationale-context") + + await expect(voteRationaleContext).toContainText(fakerContext); - // Vote context is not displayed in UI to validate - expect(false, "No vote context displayed").toBe(true); }); }); From ba53a98543bc6c4b336310dd57340511f6325c8f Mon Sep 17 00:00:00 2001 From: joseph rana Date: Mon, 11 Aug 2025 13:38:41 +0545 Subject: [PATCH 03/22] feat: add test for ipfs vote context --- .../lib/pages/governanceActionDetailsPage.ts | 39 +++++++++-------- .../proposalVisibility.dRep.spec.ts | 43 +++++++++++-------- 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts index 800b0f29c..17d51f302 100644 --- a/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts @@ -79,7 +79,7 @@ export default class GovernanceActionDetailsPage { } @withTxConfirmation - async vote(context?: string, isAlreadyVoted: boolean = false) { + async vote(context?: string, isAlreadyVoted: boolean = false , useIPFSforStorage : boolean = false ){ if (!isAlreadyVoted) { await this.yesVoteRadio.click(); } @@ -87,30 +87,33 @@ export default class GovernanceActionDetailsPage { await this.voteBtn.click() if (context) { - // await this.contextBtn.click(); await this.contextInput.fill(context); this.confirmModalBtn.click() - await this.downloadAndStoreYourselfOptionBtn.click() - - await this.page.getByRole("checkbox").click(); - await this.confirmModalBtn.click(); - - this.metadataDownloadBtn.click(); - const voteMetadata = await this.downloadVoteMetadata(); - const url = await metadataBucketService.uploadMetadata( - voteMetadata.name, - voteMetadata.data - ); - - await this.metadataUrlInput.fill(url); + if (useIPFSforStorage) { + await this.govtoolPinsDatatoIpfsBtn.click() + } else { + + await this.downloadAndStoreYourselfOptionBtn.click() + await this.page.getByRole("checkbox").click(); + await this.confirmModalBtn.click(); + + this.metadataDownloadBtn.click(); + const voteMetadata = await this.downloadVoteMetadata(); + const url = await metadataBucketService.uploadMetadata( + voteMetadata.name, + voteMetadata.data + ); + await this.metadataUrlInput.fill(url); + } await this.confirmModalBtn.click(); await this.page.getByTestId("go-to-vote-modal-button").click(); - } - // const isVoteButtonEnabled = await this.voteBtn.isEnabled(); - // await this.voteBtn.click() + } + else { + await this.confirmModalBtn.click() + } } async getDRepNotVoted( diff --git a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts index 469e32a2d..aea5f9c61 100644 --- a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts @@ -106,9 +106,7 @@ test.describe("Temporary DReps", async () => { test.beforeEach(async ({ page, browser }) => { const wallet = await walletManager.popWallet("registeredDRep"); - const tempDRepAuth = await createTempDRepAuth(page, wallet); - dRepPage = await createNewPageWithWallet(browser, { storageState: tempDRepAuth, wallet, @@ -116,32 +114,39 @@ test.describe("Temporary DReps", async () => { }); }); - test("4J. Should include metadata anchor in the vote transaction", async ({}, testInfo) => { + const verifyVoteWithMetadata = async (testInfo: any, useGovToolIPFS: boolean = false) => { test.setTimeout(testInfo.timeout + environments.txTimeOut); - + const govActionsPage = new GovernanceActionsPage(dRepPage); await govActionsPage.goto(); - + const govActionDetailsPage = await govActionsPage.viewFirstProposal(); - - const fakerContext = faker.lorem.sentence(200) - await govActionDetailsPage.vote(fakerContext); - - await dRepPage.reload();1 - await dRepPage.waitForTimeout(5_000);1 - + const fakerContext = faker.lorem.sentence(200); + + if (useGovToolIPFS) { + await govActionDetailsPage.vote(fakerContext, false, true); + } else { + await govActionDetailsPage.vote(fakerContext); + } + + await dRepPage.reload(); + await dRepPage.waitForTimeout(5_000); await govActionsPage.votedTab.click(); - const votedGovActionDetailsPage = await govActionsPage.viewFirstVotedProposal(); - - await votedGovActionDetailsPage.currentPage.getByTestId("show-more-button").click() - - await votedGovActionDetailsPage.currentPage.waitForTimeout(2000) - - const voteRationaleContext = await votedGovActionDetailsPage.currentPage.getByTestId("vote-rationale-context") + const votedGovActionDetailsPage = await govActionsPage.viewFirstVotedProposal(); + await votedGovActionDetailsPage.currentPage.getByTestId("show-more-button").click(); + await votedGovActionDetailsPage.currentPage.waitForTimeout(2000); + const voteRationaleContext = await votedGovActionDetailsPage.currentPage.getByTestId("vote-rationale-context"); await expect(voteRationaleContext).toContainText(fakerContext); + }; + + test("4J. Should include metadata anchor in the vote transaction (Download and store yourself)", async ({}, testInfo) => { + await verifyVoteWithMetadata(testInfo, false); + }); + test("4k. Should include metadata anchor in the vote transaction (GovTool pins data to IPFS)", async ({}, testInfo) => { + await verifyVoteWithMetadata(testInfo, true); }); }); From 177106d33f266a3eadb6ac993b896e9311a65688 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Mon, 11 Aug 2025 13:38:57 +0545 Subject: [PATCH 04/22] chore: increase wallet generation count --- tests/govtool-frontend/playwright/tests/dRep.setup.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/govtool-frontend/playwright/tests/dRep.setup.ts b/tests/govtool-frontend/playwright/tests/dRep.setup.ts index 584120038..c6b022822 100644 --- a/tests/govtool-frontend/playwright/tests/dRep.setup.ts +++ b/tests/govtool-frontend/playwright/tests/dRep.setup.ts @@ -12,9 +12,10 @@ import kuberService from "@services/kuberService"; import walletManager from "lib/walletManager"; import { functionWaitedAssert } from "@helpers/waitedLoop"; import { StaticWallet } from "@types"; +import { Logger } from "@helpers/logger"; const REGISTER_DREP_WALLETS_COUNT = 6; -const DREP_WALLETS_COUNT = 10; +const DREP_WALLETS_COUNT = 11; let dRepDeposit: number; @@ -73,6 +74,10 @@ setup("Register DRep of static wallets", async () => { }); setup("Setup temporary DRep wallets", async () => { + + Logger.info("KUBER API KEY") + Logger.info(process.env.KUBER_API_KEY) + const totalRequiredBalanceForDRepSetup = (DREP_WALLETS_COUNT + REGISTER_DREP_WALLETS_COUNT) * (dRepDeposit / 1000000 + 22); From e2d8e4b2f7c5d189cfa3011a26c74a21a14f6751 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Tue, 12 Aug 2025 11:35:26 +0545 Subject: [PATCH 05/22] chore: remove logs --- tests/govtool-frontend/playwright/tests/dRep.setup.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/govtool-frontend/playwright/tests/dRep.setup.ts b/tests/govtool-frontend/playwright/tests/dRep.setup.ts index c6b022822..47d4f1647 100644 --- a/tests/govtool-frontend/playwright/tests/dRep.setup.ts +++ b/tests/govtool-frontend/playwright/tests/dRep.setup.ts @@ -75,9 +75,6 @@ setup("Register DRep of static wallets", async () => { setup("Setup temporary DRep wallets", async () => { - Logger.info("KUBER API KEY") - Logger.info(process.env.KUBER_API_KEY) - const totalRequiredBalanceForDRepSetup = (DREP_WALLETS_COUNT + REGISTER_DREP_WALLETS_COUNT) * (dRepDeposit / 1000000 + 22); From 8b5e2848fcc4c23d0642d5734a34a4cf27b26c40 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Tue, 12 Aug 2025 13:08:29 +0545 Subject: [PATCH 06/22] fix: add proper proposal load page await --- .../lib/pages/governanceActionsPage.ts | 23 +++++++++++++++---- .../dRepRegistration.dRep.spec.ts | 14 ++++------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/governanceActionsPage.ts b/tests/govtool-frontend/playwright/lib/pages/governanceActionsPage.ts index d096b2965..8d269076f 100644 --- a/tests/govtool-frontend/playwright/lib/pages/governanceActionsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/governanceActionsPage.ts @@ -55,9 +55,9 @@ export default class GovernanceActionsPage { async viewFirstProposalByGovernanceAction( governanceAction: GovernanceActionType ): Promise { - const proposalCard = this.page - .getByTestId(`govaction-${governanceAction}-card`) - .first(); + const proposalCard = this.page + .locator('[data-testid^="govaction-"][data-testid$="-card"]') + .first(); const isVisible = await proposalCard.isVisible(); @@ -76,6 +76,21 @@ export default class GovernanceActionsPage { } } + async getFirstProposal( + governanceAction: GovernanceActionType + ) { + await functionWaitedAssert( + async () => { + const proposalCard = this.page + .locator('[data-testid^="govaction-"][data-testid$="-card"]') + .first(); + + await expect(proposalCard + .locator('[data-testid^="govaction-"][data-testid$="-view-detail"]') + .first()).toBeVisible() + }, { name: "Retrying to get the first proposal" }); + } + async viewVotedProposal( proposal: IProposal ): Promise { @@ -122,7 +137,7 @@ export default class GovernanceActionsPage { expect( hasFilter, hasFilter == false && - `A proposal card does not contain any of the ${filters}` + `A proposal card does not contain any of the ${filters}` ).toBe(true); } } diff --git a/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts index 1e1bdcbfa..63287bce7 100644 --- a/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts @@ -42,18 +42,12 @@ test.describe("Logged in DReps", () => { timeout: 60_000, }); - await expect( - page.getByTestId("dRep-id-display-card-dashboard") - ).toContainText(dRep01Wallet.dRepId, { timeout: 60_000 }); - + const governanceActionsPage = new GovernanceActionsPage(page); - + await governanceActionsPage.goto(); - - await expect(page.getByText(/info action/i).first()).toBeVisible({ - timeout: 60_000, - }); - + + await governanceActionsPage.getFirstProposal(GovernanceActionType.InfoAction); const governanceActionDetailsPage = await governanceActionsPage.viewFirstProposalByGovernanceAction( GovernanceActionType.InfoAction From c770507713de58a64102b0de71d8353efac40ec3 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Tue, 12 Aug 2025 15:04:11 +0545 Subject: [PATCH 07/22] fix: update outdated vote context related tests --- .../lib/pages/governanceActionDetailsPage.ts | 2 +- .../lib/pages/governanceActionsPage.ts | 1 - .../dRepRegistration.dRep.spec.ts | 2 +- .../proposalFunctionality.dRep.spec.ts | 47 ++++++++----------- 4 files changed, 22 insertions(+), 30 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts index 17d51f302..2dea0a34f 100644 --- a/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/governanceActionDetailsPage.ts @@ -21,7 +21,6 @@ export default class GovernanceActionDetailsPage { readonly externalModalBtn = this.page.getByTestId("external-modal-button"); readonly governanceActionId = this.page.getByText("Governance Action ID:"); - readonly contextBtn = this.page.getByTestId("provide-context-button"); readonly metadataDownloadBtn = this.page.getByTestId( "metadata-download-button" ); @@ -172,5 +171,6 @@ export default class GovernanceActionDetailsPage { async reVote() { await this.noVoteRadio.click(); await this.changeVoteBtn.click(); + await this.confirmModalBtn.click(); } } diff --git a/tests/govtool-frontend/playwright/lib/pages/governanceActionsPage.ts b/tests/govtool-frontend/playwright/lib/pages/governanceActionsPage.ts index 8d269076f..132b22479 100644 --- a/tests/govtool-frontend/playwright/lib/pages/governanceActionsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/governanceActionsPage.ts @@ -77,7 +77,6 @@ export default class GovernanceActionsPage { } async getFirstProposal( - governanceAction: GovernanceActionType ) { await functionWaitedAssert( async () => { diff --git a/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts index 63287bce7..3d37d3733 100644 --- a/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/3-drep-registration/dRepRegistration.dRep.spec.ts @@ -47,7 +47,7 @@ test.describe("Logged in DReps", () => { await governanceActionsPage.goto(); - await governanceActionsPage.getFirstProposal(GovernanceActionType.InfoAction); + await governanceActionsPage.getFirstProposal(); const governanceActionDetailsPage = await governanceActionsPage.viewFirstProposalByGovernanceAction( GovernanceActionType.InfoAction diff --git a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts index eb805f417..e41b0def9 100644 --- a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts @@ -72,7 +72,6 @@ test.describe("Proposal checks", () => { currentPage.getByTestId(`${cip129GovActionId}-id`) ).toBeVisible(); - await expect(govActionDetailsPage.contextBtn).toBeVisible(); await expect(govActionDetailsPage.showVotesBtn).toBeVisible(); await expect(govActionDetailsPage.voteBtn).toBeVisible(); @@ -87,12 +86,12 @@ test.describe("Proposal checks", () => { await expect(govActionDetailsPage.noVoteRadio).toBeVisible(); await expect(govActionDetailsPage.abstainRadio).toBeVisible(); - await govActionDetailsPage.contextBtn.click(); + await govActionDetailsPage.yesVoteRadio.click(); + await govActionDetailsPage.voteBtn.click(); await expect(govActionDetailsPage.contextInput).toBeVisible(); await govActionDetailsPage.cancelModalBtn.click(); - await govActionDetailsPage.yesVoteRadio.click(); await expect(govActionDetailsPage.voteBtn).toBeEnabled(); }); @@ -100,7 +99,9 @@ test.describe("Proposal checks", () => { const characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; test("5D_1. Should accept valid data in provide context", async () => { - await govActionDetailsPage.contextBtn.click(); + + await govActionDetailsPage.yesVoteRadio.click() + await govActionDetailsPage.voteBtn.click() await expect(govActionDetailsPage.contextInput).toBeVisible(); @@ -119,7 +120,8 @@ test.describe("Proposal checks", () => { }); test("5D_2. Should reject invalid data in provide context", async () => { - await govActionDetailsPage.contextBtn.click(); + await govActionDetailsPage.yesVoteRadio.click() + await govActionDetailsPage.voteBtn.click() await expect(govActionDetailsPage.contextInput).toBeVisible(); @@ -245,32 +247,23 @@ test.describe("Perform voting", () => { ).toBeVisible(); govActionDetailsPage = await governanceActionsPage.viewFirstVotedProposal(); - await govActionDetailsPage.vote(faker.lorem.sentence(200), true); + + const fakerContext = faker.lorem.sentence(200) + await govActionDetailsPage.vote(fakerContext, true); await govActionDetailsPage.currentPage.reload(); await governanceActionsPage.votedTab.click(); - const isYesVoteVisible = await govActionDetailsPage.currentPage - .getByTestId("my-vote") - .getByText("Yes") - .isVisible(); - - const textContent = await govActionDetailsPage.currentPage - .getByTestId("my-vote") - .textContent(); - - await govActionDetailsPage.currentPage.evaluate(() => - window.scrollTo(0, 500) - ); - await expect( - govActionDetailsPage.currentPage.getByTestId("my-vote").getByText("Yes"), - { - message: - !isYesVoteVisible && - `"Yes" vote not visible, current vote status: ${textContent.match(/My Vote:(Yes|No)/)[1]}`, - } - ).toBeVisible({ timeout: 60_000 }); + govActionDetailsPage = await governanceActionsPage.viewFirstVotedProposal(); + + await govActionDetailsPage.currentPage.getByTestId("yes-radio").isVisible(); + + await govActionDetailsPage.currentPage.getByTestId("show-more-button").click(); + await govActionDetailsPage.currentPage.waitForTimeout(2000); + + const voteRationaleContext = await govActionDetailsPage.currentPage.getByTestId("vote-rationale-context"); + await expect(voteRationaleContext).toContainText(fakerContext); }); test("5I. Should view the vote details,when viewing governance action already voted by the DRep", async ({}, testInfo) => { @@ -282,7 +275,7 @@ test.describe("Perform voting", () => { govActionDetailsPage.currentPage ); - await governanceActionsPage.currentPage.waitForTimeout(5_000); + await governanceActionsPage.getFirstProposal(); await governanceActionsPage.votedTab.click(); await expect( From 551821132b28e3f41f7dd96e5ebf6dfbfeefecac Mon Sep 17 00:00:00 2001 From: joseph rana Date: Fri, 15 Aug 2025 13:27:03 +0545 Subject: [PATCH 08/22] fix undefined data test id issue for image input --- govtool/frontend/src/components/molecules/DRepDataForm.tsx | 2 +- .../src/components/organisms/UncontrolledImageInput.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/govtool/frontend/src/components/molecules/DRepDataForm.tsx b/govtool/frontend/src/components/molecules/DRepDataForm.tsx index 6b2a65506..e5b077b49 100644 --- a/govtool/frontend/src/components/molecules/DRepDataForm.tsx +++ b/govtool/frontend/src/components/molecules/DRepDataForm.tsx @@ -107,7 +107,7 @@ export const DRepDataForm = ({ control, errors, register, watch }: Props) => { subtitle={t("forms.dRepData.imageHelpfulText")} /> ({ /> {fieldState.error && ( )} From eab2adb1e726f60ec5f83ef658c0207bb80af1a8 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Fri, 15 Aug 2025 13:28:05 +0545 Subject: [PATCH 09/22] fix:update image input related data test id and display message --- tests/govtool-frontend/playwright/lib/forms/dRepForm.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/forms/dRepForm.ts b/tests/govtool-frontend/playwright/lib/forms/dRepForm.ts index 63516b43a..74d6fe61c 100644 --- a/tests/govtool-frontend/playwright/lib/forms/dRepForm.ts +++ b/tests/govtool-frontend/playwright/lib/forms/dRepForm.ts @@ -13,7 +13,7 @@ const formErrors = { ], linkDescription: "max-80-characters-error", email: "invalid-email-address-error", - image: "invalid-image-url-error", + image: "invalid-image-input-error", links: { url: "link-reference-description-1-error", description: "link-reference-description-1-error", @@ -304,7 +304,7 @@ export default class DRepForm { }).not.toEqual(dRepInfo.qualifications); await expect(this.form.getByTestId(formErrors.image), { - message: !isImageErrorVisible && `${dRepInfo.image} is a valid image`, + message: !isImageErrorVisible && `Invalid image URL or properly formatted base64-encoded image`, }).toBeVisible({ timeout: 60_000, }); From 931c12eb99693045fa31c2929f820cc9bc99cee4 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Fri, 15 Aug 2025 14:02:05 +0545 Subject: [PATCH 10/22] fix: replace show all btn with link in governance actions page --- .../tests/4-proposal-visibility/proposalVisibility.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.spec.ts b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.spec.ts index adab72a92..740935a99 100644 --- a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.spec.ts +++ b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.spec.ts @@ -189,7 +189,7 @@ test("4M. Should show view-all categorized governance actions", async ({ const governanceActionPage = new GovernanceActionsPage(page); await governanceActionPage.goto(); - await page.getByRole("button", { name: "Show All" }).click(); + await page.getByRole("link", { name: "Show All" }).click(); const proposalCards = await governanceActionPage.getAllProposals(); From 8b3fdaae756c06b3a508c7296ffdd3bd2100de1f Mon Sep 17 00:00:00 2001 From: joseph rana Date: Fri, 15 Aug 2025 14:52:53 +0545 Subject: [PATCH 11/22] chore: move voting tests to proposal functionality --- .../proposalVisibility.dRep.spec.ts | 1 - .../proposalFunctionality.dRep.spec.ts | 49 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts index aea5f9c61..1e7c1e1e1 100644 --- a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts @@ -67,7 +67,6 @@ test.describe("Logged in DRep", () => { ) : await govActionsPage.viewFirstProposal(); - await govActionDetailsPage.contextBtn.click(); await govActionDetailsPage.contextInput.fill(faker.lorem.sentence(200)); await govActionDetailsPage.confirmModalBtn.click(); await page.getByRole("checkbox").click(); diff --git a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts index e41b0def9..15d0a6c4e 100644 --- a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts @@ -315,3 +315,52 @@ test.describe("Check voting power", () => { expect(balance, "Retirement deposit not returned").toBeGreaterThan(500); }); }); + +test.describe("Temporary DReps Voting", async () => { + let dRepPage: Page; + + test.beforeEach(async ({ page, browser }) => { + const wallet = await walletManager.popWallet("registeredDRep"); + const tempDRepAuth = await createTempDRepAuth(page, wallet); + dRepPage = await createNewPageWithWallet(browser, { + storageState: tempDRepAuth, + wallet, + enableDRepSigning: true, + }); + }); + + const verifyVoteWithMetadata = async (testInfo: any, useGovToolIPFS: boolean = false) => { + test.setTimeout(testInfo.timeout + environments.txTimeOut); + + const govActionsPage = new GovernanceActionsPage(dRepPage); + await govActionsPage.goto(); + + const govActionDetailsPage = await govActionsPage.viewFirstProposal(); + const fakerContext = faker.lorem.sentence(200); + + if (useGovToolIPFS) { + await govActionDetailsPage.vote(fakerContext, false, true); + } else { + await govActionDetailsPage.vote(fakerContext); + } + + await dRepPage.reload(); + await dRepPage.waitForTimeout(5_000); + await govActionsPage.votedTab.click(); + + const votedGovActionDetailsPage = await govActionsPage.viewFirstVotedProposal(); + await votedGovActionDetailsPage.currentPage.getByTestId("show-more-button").click(); + await votedGovActionDetailsPage.currentPage.waitForTimeout(2000); + + const voteRationaleContext = await votedGovActionDetailsPage.currentPage.getByTestId("vote-rationale-context"); + await expect(voteRationaleContext).toContainText(fakerContext); + }; + + test("5M. Should vote with Context (Download and store yourself)", async ({}, testInfo) => { + await verifyVoteWithMetadata(testInfo, false); + }); + + test("4N. Should vote with Context (GovTool pins data to IPFS)", async ({}, testInfo) => { + await verifyVoteWithMetadata(testInfo, true); + }); +}); \ No newline at end of file From 42ecf9eb7abbf595002f62e1070b84751dd04fd4 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Fri, 15 Aug 2025 15:04:58 +0545 Subject: [PATCH 12/22] add include metadata anchor in vote transaction test --- .../proposalVisibility.dRep.spec.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts index 1e7c1e1e1..3000c0ce8 100644 --- a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts @@ -113,7 +113,7 @@ test.describe("Temporary DReps", async () => { }); }); - const verifyVoteWithMetadata = async (testInfo: any, useGovToolIPFS: boolean = false) => { + const verifyVoteWithMetadata = async (testInfo: any) => { test.setTimeout(testInfo.timeout + environments.txTimeOut); const govActionsPage = new GovernanceActionsPage(dRepPage); @@ -122,11 +122,7 @@ test.describe("Temporary DReps", async () => { const govActionDetailsPage = await govActionsPage.viewFirstProposal(); const fakerContext = faker.lorem.sentence(200); - if (useGovToolIPFS) { - await govActionDetailsPage.vote(fakerContext, false, true); - } else { - await govActionDetailsPage.vote(fakerContext); - } + await govActionDetailsPage.vote(fakerContext); await dRepPage.reload(); await dRepPage.waitForTimeout(5_000); @@ -140,12 +136,8 @@ test.describe("Temporary DReps", async () => { await expect(voteRationaleContext).toContainText(fakerContext); }; - test("4J. Should include metadata anchor in the vote transaction (Download and store yourself)", async ({}, testInfo) => { - await verifyVoteWithMetadata(testInfo, false); - }); - - test("4k. Should include metadata anchor in the vote transaction (GovTool pins data to IPFS)", async ({}, testInfo) => { - await verifyVoteWithMetadata(testInfo, true); + test("4J. Should include metadata anchor in the vote transaction", async ({}, testInfo) => { + await verifyVoteWithMetadata(testInfo); }); }); From 8b03f189e97f968782ffab820e878fe1473c80ff Mon Sep 17 00:00:00 2001 From: joseph rana Date: Fri, 22 Aug 2025 14:16:32 +0545 Subject: [PATCH 13/22] fix: handle outcomeResponse as single object instead of array --- .../govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts index cbf39d9ca..a22b56110 100644 --- a/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts @@ -111,7 +111,7 @@ export default class OutcomeDetailsPage { } const outcomeResponse = await outcomeResponsePromise; - const proposalToCheck = (await outcomeResponse.json())[0]; + const proposalToCheck = (await outcomeResponse.json()); const metricsResponse = await metricsResponsePromise; From b8b845c80db2557fe1d36585c36bc077d961f4ba Mon Sep 17 00:00:00 2001 From: joseph rana Date: Fri, 22 Aug 2025 16:14:09 +0545 Subject: [PATCH 14/22] chore: remove duplicate test --- .../playwright/lib/pages/outcomesPage.ts | 4 +- .../proposalVisibility.dRep.spec.ts | 41 ------------------- .../proposalFunctionality.dRep.spec.ts | 4 +- .../playwright/tests/dRep.setup.ts | 3 +- 4 files changed, 5 insertions(+), 47 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/outcomesPage.ts b/tests/govtool-frontend/playwright/lib/pages/outcomesPage.ts index 97d146349..32fe6dd98 100644 --- a/tests/govtool-frontend/playwright/lib/pages/outcomesPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/outcomesPage.ts @@ -579,7 +579,7 @@ export default class OutComesPage { const metricsResponsePromise = page.waitForResponse( (response) => response.url().includes(`/misc/network/metrics?epoch`), - { timeout: 60_000 } + { timeout: 120_000 } ); expect( @@ -597,7 +597,7 @@ export default class OutComesPage { .includes( `governance-actions/${governanceTransactionHash}?index=${governanceActionIndex}` ), - { timeout: 60_000 } + { timeout: 120_000 } ); const govActionDetailsPage = await outcomePage.viewFirstOutcomes(); diff --git a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts index 3000c0ce8..ff40cf000 100644 --- a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts @@ -100,47 +100,6 @@ test.describe("Logged in DRep", () => { }); }); -test.describe("Temporary DReps", async () => { - let dRepPage: Page; - - test.beforeEach(async ({ page, browser }) => { - const wallet = await walletManager.popWallet("registeredDRep"); - const tempDRepAuth = await createTempDRepAuth(page, wallet); - dRepPage = await createNewPageWithWallet(browser, { - storageState: tempDRepAuth, - wallet, - enableDRepSigning: true, - }); - }); - - const verifyVoteWithMetadata = async (testInfo: any) => { - test.setTimeout(testInfo.timeout + environments.txTimeOut); - - const govActionsPage = new GovernanceActionsPage(dRepPage); - await govActionsPage.goto(); - - const govActionDetailsPage = await govActionsPage.viewFirstProposal(); - const fakerContext = faker.lorem.sentence(200); - - await govActionDetailsPage.vote(fakerContext); - - await dRepPage.reload(); - await dRepPage.waitForTimeout(5_000); - await govActionsPage.votedTab.click(); - - const votedGovActionDetailsPage = await govActionsPage.viewFirstVotedProposal(); - await votedGovActionDetailsPage.currentPage.getByTestId("show-more-button").click(); - await votedGovActionDetailsPage.currentPage.waitForTimeout(2000); - - const voteRationaleContext = await votedGovActionDetailsPage.currentPage.getByTestId("vote-rationale-context"); - await expect(voteRationaleContext).toContainText(fakerContext); - }; - - test("4J. Should include metadata anchor in the vote transaction", async ({}, testInfo) => { - await verifyVoteWithMetadata(testInfo); - }); -}); - test.describe("Check vote count", () => { test.use({ storageState: dRep01AuthFile, wallet: dRep01Wallet }); diff --git a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts index 15d0a6c4e..7ec1f3a38 100644 --- a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts @@ -356,11 +356,11 @@ test.describe("Temporary DReps Voting", async () => { await expect(voteRationaleContext).toContainText(fakerContext); }; - test("5M. Should vote with Context (Download and store yourself)", async ({}, testInfo) => { + test("5M_1. Should vote with Context (Download and store yourself)", async ({}, testInfo) => { await verifyVoteWithMetadata(testInfo, false); }); - test("4N. Should vote with Context (GovTool pins data to IPFS)", async ({}, testInfo) => { + test("5M_2. Should vote with Context (GovTool pins data to IPFS)", async ({}, testInfo) => { await verifyVoteWithMetadata(testInfo, true); }); }); \ No newline at end of file diff --git a/tests/govtool-frontend/playwright/tests/dRep.setup.ts b/tests/govtool-frontend/playwright/tests/dRep.setup.ts index 47d4f1647..7268baeb7 100644 --- a/tests/govtool-frontend/playwright/tests/dRep.setup.ts +++ b/tests/govtool-frontend/playwright/tests/dRep.setup.ts @@ -12,10 +12,9 @@ import kuberService from "@services/kuberService"; import walletManager from "lib/walletManager"; import { functionWaitedAssert } from "@helpers/waitedLoop"; import { StaticWallet } from "@types"; -import { Logger } from "@helpers/logger"; const REGISTER_DREP_WALLETS_COUNT = 6; -const DREP_WALLETS_COUNT = 11; +const DREP_WALLETS_COUNT = 10; let dRepDeposit: number; From e438448dca44cc96329d6c52bca8251a7885e33a Mon Sep 17 00:00:00 2001 From: joseph rana Date: Mon, 25 Aug 2025 13:18:48 +0545 Subject: [PATCH 15/22] fix: udpate gov action api call regex --- .../playwright/lib/pages/outcomeDetailsPage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts index a22b56110..668997f2c 100644 --- a/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts @@ -295,10 +295,10 @@ export default class OutcomeDetailsPage { url: string; hash: string; }) { - await this.page.route(/.*\/governance-actions\/[a-f0-9]{64}\?.*/, (route) => + await this.page.route("**/governance-actions*", (route) => route.fulfill({ body: JSON.stringify([outcomeResponse]) }) ); - + const outcomePage = new OutComesPage(this.page); await outcomePage.goto(); await outcomePage.viewFirstOutcomes(); From 2ae6c66fc465892ec66b9e78c96c410345670a89 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Mon, 25 Aug 2025 14:50:46 +0545 Subject: [PATCH 16/22] fix gov action network interception response --- .../playwright/lib/pages/outcomeDetailsPage.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts index 668997f2c..6ead1d8be 100644 --- a/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts @@ -188,7 +188,7 @@ export default class OutcomeDetailsPage { filterKey === "NoConfidence" ? proposalToCheck.pool_no_votes : parseInt(sPosNoConfidence.replace(/,/g, "")) * 1000000 + - parseInt(proposalToCheck.pool_no_votes); + parseInt(proposalToCheck.pool_no_votes); const totalSposYesVotesForNoConfidence = parseInt(sPosNoConfidence.replace(/,/g, "")) * 1000000 + @@ -295,19 +295,24 @@ export default class OutcomeDetailsPage { url: string; hash: string; }) { - await this.page.route("**/governance-actions*", (route) => - route.fulfill({ body: JSON.stringify([outcomeResponse]) }) + let governanceActionPromise = this.page.route("**/governance-actions/*", async (route) => { + if (route.request().url().includes("/governance-actions/metadata")) { + await route.continue(); + } else { + await route.fulfill({ body: JSON.stringify(outcomeResponse)}); + } + } ); - const outcomePage = new OutComesPage(this.page); await outcomePage.goto(); await outcomePage.viewFirstOutcomes(); + await governanceActionPromise; const outcomeTitle = await outcomePage.title.textContent(); await expect( outcomePage.title, outcomeTitle.toLowerCase() !== type.toLowerCase() && - `The URL "${url}" and hash "${hash}" do not match the expected properties for type "${type}".` + `The URL "${url}" and hash "${hash}" do not match the expected properties for type "${type}".` ).toHaveText(type, { ignoreCase: true, timeout: 60_000, From 1b4791660f16517eb4f60f483752127748ac919b Mon Sep 17 00:00:00 2001 From: Adam Tomaszczyk Date: Tue, 26 Aug 2025 11:21:41 +0200 Subject: [PATCH 17/22] DRep search improvements #4030 --- govtool/backend/sql/list-dreps.sql | 8 +- govtool/backend/src/VVA/DRep.hs | 7 +- .../components/molecules/DataActionsBar.tsx | 18 ++- .../queries/useGetDRepListInfiniteQuery.ts | 118 ++++++++++++++++ .../src/hooks/queries/useGetDRepListQuery.ts | 131 +++++++++++------- .../hooks/queries/useGetDrepDetailsQuery.ts | 2 +- govtool/frontend/src/i18n/locales/en.json | 1 + .../src/pages/DRepDirectoryContent.tsx | 95 +++++++++---- 8 files changed, 293 insertions(+), 87 deletions(-) create mode 100644 govtool/frontend/src/hooks/queries/useGetDRepListInfiniteQuery.ts diff --git a/govtool/backend/sql/list-dreps.sql b/govtool/backend/sql/list-dreps.sql index 1ae00d6fa..0d7be1697 100644 --- a/govtool/backend/sql/list-dreps.sql +++ b/govtool/backend/sql/list-dreps.sql @@ -317,10 +317,6 @@ WHERE ( COALESCE(?, '') = '' OR (CASE WHEN LENGTH(?) % 2 = 0 AND ? ~ '^[0-9a-fA-F]+$' THEN drep_hash = ? ELSE false END) OR - view ILIKE ? OR - given_name ILIKE ? OR - payment_address ILIKE ? OR - objectives ILIKE ? OR - motivations ILIKE ? OR - qualifications ILIKE ? + (CASE WHEN lower(?) ~ '^drep1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]+$' THEN view = lower(?) ELSE FALSE END) OR + given_name ILIKE ? ) diff --git a/govtool/backend/src/VVA/DRep.hs b/govtool/backend/src/VVA/DRep.hs index 39342a7eb..8e6f7ceae 100644 --- a/govtool/backend/src/VVA/DRep.hs +++ b/govtool/backend/src/VVA/DRep.hs @@ -86,12 +86,9 @@ listDReps mSearchQuery = withPool $ \conn -> do , searchParam -- LENGTH(?) , searchParam -- AND ? , searchParam -- decode(?, 'hex') - , "%" <> searchParam <> "%" -- dh.view + , searchParam -- lower(?) + , searchParam -- lower(?) , "%" <> searchParam <> "%" -- given_name - , "%" <> searchParam <> "%" -- payment_address - , "%" <> searchParam <> "%" -- objectives - , "%" <> searchParam <> "%" -- motivations - , "%" <> searchParam <> "%" -- qualifications ) :: IO [DRepQueryResult]) timeZone <- liftIO getCurrentTimeZone diff --git a/govtool/frontend/src/components/molecules/DataActionsBar.tsx b/govtool/frontend/src/components/molecules/DataActionsBar.tsx index 223fef97e..17183f3e0 100644 --- a/govtool/frontend/src/components/molecules/DataActionsBar.tsx +++ b/govtool/frontend/src/components/molecules/DataActionsBar.tsx @@ -1,6 +1,7 @@ import { Dispatch, FC, SetStateAction } from "react"; -import { Box, InputBase } from "@mui/material"; +import { Box, InputBase, IconButton } from "@mui/material"; import Search from "@mui/icons-material/Search"; +import CloseIcon from "@mui/icons-material/Close"; import { DataActionsFilters, DataActionsSorting } from "@molecules"; import { OrderActionsChip } from "./OrderActionsChip"; @@ -20,6 +21,7 @@ type DataActionsBarProps = { filtersTitle?: string; isFiltering?: boolean; searchText: string; + placeholder?: string; setChosenFilters?: Dispatch>; setChosenSorting: Dispatch>; setFiltersOpen?: Dispatch>; @@ -51,6 +53,7 @@ export const DataActionsBar: FC = ({ ...props }) => { setSortOpen, sortOpen, sortOptions = [], + placeholder = "Search...", } = props; const { palette: { boxShadow2 }, @@ -61,7 +64,7 @@ export const DataActionsBar: FC = ({ ...props }) => { setSearchText(e.target.value)} - placeholder="Search..." + placeholder={placeholder} value={searchText} startAdornment={ = ({ ...props }) => { }} /> } + endAdornment={ + searchText && ( + setSearchText("")} + sx={{ ml: 1 }} + > + + + ) + } sx={{ bgcolor: "white", border: 1, diff --git a/govtool/frontend/src/hooks/queries/useGetDRepListInfiniteQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepListInfiniteQuery.ts new file mode 100644 index 000000000..2465d9926 --- /dev/null +++ b/govtool/frontend/src/hooks/queries/useGetDRepListInfiniteQuery.ts @@ -0,0 +1,118 @@ +import { + UseInfiniteQueryOptions, + useInfiniteQuery, + useQuery, +} from "react-query"; +import { useRef, useMemo } from "react"; + +import { QUERY_KEYS } from "@consts"; +import { useCardano } from "@context"; +import { GetDRepListArguments, getDRepList } from "@services"; +import { DRepData, Infinite } from "@/models"; + +const makeStatusKey = (status?: string[] | undefined) => + (status && status.length ? [...status].sort().join("|") : "__EMPTY__"); + +export const useGetDRepListInfiniteQuery = ( + { + filters = [], + pageSize = 10, + searchPhrase, + sorting, + status, + }: GetDRepListArguments, + options?: UseInfiniteQueryOptions>, +) => { + const { pendingTransaction } = useCardano(); + const totalsByStatusRef = useRef>({}); + const statusKey = useMemo(() => makeStatusKey(status), [status]); + + const { + data, + isLoading, + fetchNextPage, + hasNextPage, + isFetching, + isFetchingNextPage, + isPreviousData, + } = useInfiniteQuery( + [ + QUERY_KEYS.useGetDRepListInfiniteKey, + ( + pendingTransaction.registerAsDirectVoter || + pendingTransaction.registerAsDrep || + pendingTransaction.retireAsDirectVoter || + pendingTransaction.retireAsDrep + )?.transactionHash ?? "noPendingTransaction", + filters.length ? filters : "", + searchPhrase ?? "", + sorting ?? "", + status?.length ? status : "", + ], + async ({ pageParam = 0 }) => + getDRepList({ + page: pageParam, + pageSize, + filters, + searchPhrase, + sorting, + status, + }), + { + getNextPageParam: (lastPage) => { + if (lastPage.elements.length === 0) return undefined; + return lastPage.page + 1; + }, + enabled: options?.enabled, + keepPreviousData: options?.keepPreviousData, + onSuccess: (pagesData) => { + if (!searchPhrase) { + const firstPage = pagesData.pages?.[0]; + if (firstPage && typeof firstPage.total === "number") { + totalsByStatusRef.current[statusKey] = firstPage.total; + } + } + options?.onSuccess?.(pagesData); + }, + }, + ); + + useQuery( + [QUERY_KEYS.useGetDRepListInfiniteKey, "baseline", statusKey], + async () => { + const resp = await getDRepList({ + page: 0, + pageSize: 1, + filters, + searchPhrase: "", + sorting, + status, + }); + return resp; + }, + { + enabled: + options?.enabled && + searchPhrase !== "" && + totalsByStatusRef.current[statusKey] === undefined, + onSuccess: (resp) => { + if (typeof resp.total === "number") { + totalsByStatusRef.current[statusKey] = resp.total; + } + }, + }, + ); + + return { + dRepListFetchNextPage: fetchNextPage, + dRepListHasNextPage: hasNextPage, + isDRepListFetching: isFetching, + isDRepListFetchingNextPage: isFetchingNextPage, + isDRepListLoading: isLoading, + dRepData: data?.pages.flatMap((page) => page.elements), + isPreviousData, + dRepListTotal: data?.pages[0].total, + dRepTotalsByStatus: totalsByStatusRef.current, + dRepBaselineTotalForStatus: totalsByStatusRef.current[statusKey], + }; +}; diff --git a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts index c6f0f968b..5eedc12df 100644 --- a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts @@ -1,47 +1,58 @@ -import { UseInfiniteQueryOptions, useInfiniteQuery } from "react-query"; +import { useMemo, useRef } from "react"; +import { useQuery, UseQueryOptions } from "react-query"; import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; import { GetDRepListArguments, getDRepList } from "@services"; import { DRepData, Infinite } from "@/models"; -export const useGetDRepListInfiniteQuery = ( - { - filters = [], - pageSize = 10, - searchPhrase, - sorting, - status, - }: GetDRepListArguments, - options?: UseInfiniteQueryOptions>, -) => { +const makeStatusKey = (status?: string[] | undefined) => + (status && status.length ? [...status].sort().join("|") : "__EMPTY__"); + +type PaginatedResult = { + dRepData: DRepData[] | undefined; + isLoading: boolean; + isFetching: boolean; + isPreviousData: boolean; + total: number | undefined; + baselineTotalForStatus: number | undefined; +}; + +type Args = GetDRepListArguments & { + page: number; + pageSize?: number; +}; + +export function useGetDRepListPaginatedQuery( + { page, pageSize = 10, filters = [], searchPhrase, sorting, status }: Args, + options?: UseQueryOptions>, +): PaginatedResult { const { pendingTransaction } = useCardano(); + const totalsByStatusRef = useRef>({}); + const statusKey = useMemo(() => makeStatusKey(status), [status]); - const { - data, - isLoading, - fetchNextPage, - hasNextPage, - isFetching, - isFetchingNextPage, - isPreviousData, - } = useInfiniteQuery( - [ - QUERY_KEYS.useGetDRepListInfiniteKey, - ( - pendingTransaction.registerAsDirectVoter || - pendingTransaction.registerAsDrep || - pendingTransaction.retireAsDirectVoter || - pendingTransaction.retireAsDrep - )?.transactionHash ?? "noPendingTransaction", - filters.length ? filters : "", - searchPhrase ?? "", - sorting ?? "", - status?.length ? status : "", - ], - async ({ pageParam = 0 }) => + const queryKey = [ + QUERY_KEYS.useGetDRepListInfiniteKey, + ( + pendingTransaction.registerAsDirectVoter || + pendingTransaction.registerAsDrep || + pendingTransaction.retireAsDirectVoter || + pendingTransaction.retireAsDrep + )?.transactionHash ?? "noPendingTransaction", + "paged", + page, + pageSize, + filters.length ? filters : "", + searchPhrase ?? "", + sorting ?? "", + status?.length ? status : "", + ]; + + const { data, isLoading, isFetching, isPreviousData } = useQuery( + queryKey, + async () => getDRepList({ - page: pageParam, + page, pageSize, filters, searchPhrase, @@ -49,25 +60,49 @@ export const useGetDRepListInfiniteQuery = ( status, }), { - getNextPageParam: (lastPage) => { - if (lastPage.elements.length === 0) { - return undefined; + keepPreviousData: true, + enabled: options?.enabled, + onSuccess: (resp) => { + if (!searchPhrase && typeof resp?.total === "number") { + totalsByStatusRef.current[statusKey] = resp.total; } + options?.onSuccess?.(resp); + }, + }, + ); - return lastPage.page + 1; + useQuery( + [QUERY_KEYS.useGetDRepListInfiniteKey, "baseline", statusKey], + async () => { + const resp = await getDRepList({ + page: 0, + pageSize: 1, + filters, + searchPhrase: "", + sorting, + status, + }); + return resp; + }, + { + enabled: + options?.enabled && + searchPhrase !== "" && + totalsByStatusRef.current[statusKey] === undefined, + onSuccess: (resp) => { + if (typeof resp.total === "number") { + totalsByStatusRef.current[statusKey] = resp.total; + } }, - enabled: options?.enabled, - keepPreviousData: options?.keepPreviousData, }, ); return { - dRepListFetchNextPage: fetchNextPage, - dRepListHasNextPage: hasNextPage, - isDRepListFetching: isFetching, - isDRepListFetchingNextPage: isFetchingNextPage, - isDRepListLoading: isLoading, - dRepData: data?.pages.flatMap((page) => page.elements), + dRepData: data?.elements, + isLoading, + isFetching, isPreviousData, + total: data?.total, + baselineTotalForStatus: totalsByStatusRef.current[statusKey], }; -}; +} diff --git a/govtool/frontend/src/hooks/queries/useGetDrepDetailsQuery.ts b/govtool/frontend/src/hooks/queries/useGetDrepDetailsQuery.ts index 3c99959ff..d4e39aefa 100644 --- a/govtool/frontend/src/hooks/queries/useGetDrepDetailsQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDrepDetailsQuery.ts @@ -1,7 +1,7 @@ import { UseInfiniteQueryOptions } from "react-query"; import { Infinite, DRepData } from "@/models"; -import { useGetDRepListInfiniteQuery } from "./useGetDRepListQuery"; +import { useGetDRepListInfiniteQuery } from "./useGetDRepListInfiniteQuery"; export const useGetDRepDetailsQuery = ( dRepId: string | null | undefined, diff --git a/govtool/frontend/src/i18n/locales/en.json b/govtool/frontend/src/i18n/locales/en.json index cca6a27e3..c4b054471 100644 --- a/govtool/frontend/src/i18n/locales/en.json +++ b/govtool/frontend/src/i18n/locales/en.json @@ -314,6 +314,7 @@ "myDelegationToYourself": "You have delegated ₳ {{ada}} to yourself", "myDRep": "You have delegated ₳ {{ada}} to this DRep", "listTitle": "Find a DRep", + "searchBarPlaceholder": "Search for a DRep name or ID", "noConfidenceDefaultDescription": "Select this to signal no confidence in the current constitutional committee by voting NO on every proposal and voting YES to no confidence proposals", "noConfidenceDefaultTitle": "Signal No Confidence on Every Vote", "noResultsForTheSearchTitle": "No DReps found", diff --git a/govtool/frontend/src/pages/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx index 47b1b1006..f95ad447c 100644 --- a/govtool/frontend/src/pages/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx @@ -1,8 +1,8 @@ -import { FC, useEffect, useState } from "react"; +import React, { FC, useEffect, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; -import { Box, CircularProgress } from "@mui/material"; +import { Box, CircularProgress, Pagination } from "@mui/material"; -import { Button, Typography } from "@atoms"; +import { Typography } from "@atoms"; import { DREP_DIRECTORY_FILTERS, DREP_DIRECTORY_SORTING } from "@consts"; import { useCardano, useDataActionsBar } from "@context"; import { @@ -10,7 +10,7 @@ import { useGetAdaHolderCurrentDelegationQuery, useGetAdaHolderVotingPowerQuery, useGetDRepDetailsQuery, - useGetDRepListInfiniteQuery, + useGetDRepListPaginatedQuery, } from "@hooks"; import { DataActionsBar, EmptyStateDrepDirectory } from "@molecules"; import { AutomatedVotingOptions, DRepCard } from "@organisms"; @@ -64,8 +64,12 @@ export const DRepDirectoryContent: FC = ({ const [inProgressDelegationDRepData, setInProgressDelegationDRepData] = useState(undefined); + const [page, setPage] = useState(1); + const pageSize = 10; + // Set initial filters and sort useEffect(() => { + // TODO: it should be done only if last page URL WASN'T like /drep_directory/drep1.* setChosenFilters([DRepStatus.Active]); setSearchText(""); // <--- Clear the search field on mount }, []); @@ -74,6 +78,11 @@ export const DRepDirectoryContent: FC = ({ if (!chosenSorting) setChosenSorting(DRepListSort.Random); }, [chosenSorting, setChosenSorting]); + useEffect(() => { + // TODO: it should be done only if last page URL WASN'T like /drep_directory/drep1.* + setPage(1); + }, [debouncedSearchText, chosenSorting, JSON.stringify(chosenFilters)]); + const { delegate, isDelegating } = useDelegateTodRep(); const { votingPower } = useGetAdaHolderVotingPowerQuery(stakeKey); @@ -87,21 +96,26 @@ export const DRepDirectoryContent: FC = ({ const { dRepData: dRepList, - isPreviousData, - dRepListHasNextPage, - dRepListFetchNextPage, - } = useGetDRepListInfiniteQuery( + isFetching, + isPreviousData: isPrev, + total, + baselineTotalForStatus, + } = useGetDRepListPaginatedQuery( { + page: page - 1, // convert 1-based UI -> 0-based API + pageSize, searchPhrase: debouncedSearchText, sorting: chosenSorting as DRepListSort, status: chosenFilters as DRepStatus[], }, - { - enabled: !!chosenSorting, - keepPreviousData: true, - }, + { enabled: !!chosenSorting }, ); + const showSearchSummary = + searchText !== "" && + (!isFetching || !isPrev) && + total !== baselineTotalForStatus; + useEffect(() => { if (!inProgressDelegation && prevInProgressDelegation) { setInProgressDelegationDRepData(undefined); @@ -128,6 +142,9 @@ export const DRepDirectoryContent: FC = ({ "view", ); + const totalForPaging = typeof total === "number" ? total : 0; + const pageCount = Math.max(1, Math.ceil(totalForPaging / pageSize)); + const isAnAutomatedVotingOptionChosen = currentDelegation?.dRepView && (currentDelegation?.dRepView === @@ -212,16 +229,41 @@ export const DRepDirectoryContent: FC = ({ filterOptions={DREP_DIRECTORY_FILTERS} filtersTitle={t("dRepDirectory.filterTitle")} sortOptions={DREP_DIRECTORY_SORTING} + placeholder={t("dRepDirectory.searchBarPlaceholder")} /> + + {showSearchSummary && ( + + + ) : ( + + ), + }} + /> + + )} = ({ ))} + + {pageCount > 1 && ( + + setPage(newPage)} + shape="rounded" + variant="outlined" + siblingCount={1} + boundaryCount={1} + /> + + )} - {dRepListHasNextPage && dRepList.length >= 10 && ( - - - - )} ); }; From 3b52ae102c77d6e28b255902f5d672f271d8d2f2 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Wed, 27 Aug 2025 10:46:12 +0545 Subject: [PATCH 18/22] chore: increase wallet generation count --- tests/govtool-frontend/playwright/tests/dRep.setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/govtool-frontend/playwright/tests/dRep.setup.ts b/tests/govtool-frontend/playwright/tests/dRep.setup.ts index 7268baeb7..d4c2e9936 100644 --- a/tests/govtool-frontend/playwright/tests/dRep.setup.ts +++ b/tests/govtool-frontend/playwright/tests/dRep.setup.ts @@ -14,7 +14,7 @@ import { functionWaitedAssert } from "@helpers/waitedLoop"; import { StaticWallet } from "@types"; const REGISTER_DREP_WALLETS_COUNT = 6; -const DREP_WALLETS_COUNT = 10; +const DREP_WALLETS_COUNT = 11; let dRepDeposit: number; From fd84704ecfce056685686ec199ea3d6e6257808f Mon Sep 17 00:00:00 2001 From: joseph rana Date: Wed, 27 Aug 2025 11:46:09 +0545 Subject: [PATCH 19/22] refactor: move voting related tests to perform voting group --- .../proposalFunctionality.dRep.spec.ts | 87 ++++++++----------- 1 file changed, 35 insertions(+), 52 deletions(-) diff --git a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts index 7ec1f3a38..4d8052543 100644 --- a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts @@ -138,6 +138,8 @@ test.describe("Proposal checks", () => { test.describe("Perform voting", () => { let govActionDetailsPage: GovernanceActionDetailsPage; + let dRepPage: Page; + let govActionsPage: GovernanceActionsPage; test.beforeEach(async ({ page, browser }) => { test.slow(); // Due to queue in pop wallets @@ -146,14 +148,14 @@ test.describe("Perform voting", () => { const tempDRepAuth = await createTempDRepAuth(page, wallet); - const dRepPage = await createNewPageWithWallet(browser, { + dRepPage = await createNewPageWithWallet(browser, { storageState: tempDRepAuth, wallet, enableDRepSigning: true, }); - const govActionsPage = new GovernanceActionsPage(dRepPage); - await govActionsPage.goto(); + govActionsPage = new GovernanceActionsPage(dRepPage); + govActionsPage.goto(); // assert to wait until the loading button is hidden await expect(dRepPage.getByTestId("to-vote-tab")).toBeVisible({ @@ -282,6 +284,36 @@ test.describe("Perform voting", () => { govActionDetailsPage.currentPage.getByTestId("my-vote").getByText("Yes") ).toBeVisible(); }); + + const verifyVoteWithMetadata = async (testInfo: any, useGovToolIPFS: boolean = false) => { + test.setTimeout(testInfo.timeout + environments.txTimeOut); + const fakerContext = faker.lorem.sentence(200); + + if (useGovToolIPFS) { + await govActionDetailsPage.vote(fakerContext, false, true); + } else { + await govActionDetailsPage.vote(fakerContext); + } + + await dRepPage.reload(); + await dRepPage.waitForTimeout(5_000); + await govActionsPage.votedTab.click(); + + const votedGovActionDetailsPage = await govActionsPage.viewFirstVotedProposal(); + await votedGovActionDetailsPage.currentPage.getByTestId("show-more-button").click(); + await votedGovActionDetailsPage.currentPage.waitForTimeout(2000); + + const voteRationaleContext = await votedGovActionDetailsPage.currentPage.getByTestId("vote-rationale-context"); + await expect(voteRationaleContext).toContainText(fakerContext); + }; + + test("5M_1. Should vote with Context (Download and store yourself)", async ({}, testInfo) => { + await verifyVoteWithMetadata(testInfo, false); + }); + + test("5M_2. Should vote with Context (GovTool pins data to IPFS)", async ({}, testInfo) => { + await verifyVoteWithMetadata(testInfo, true ); + }); }); test.describe("Check voting power", () => { @@ -315,52 +347,3 @@ test.describe("Check voting power", () => { expect(balance, "Retirement deposit not returned").toBeGreaterThan(500); }); }); - -test.describe("Temporary DReps Voting", async () => { - let dRepPage: Page; - - test.beforeEach(async ({ page, browser }) => { - const wallet = await walletManager.popWallet("registeredDRep"); - const tempDRepAuth = await createTempDRepAuth(page, wallet); - dRepPage = await createNewPageWithWallet(browser, { - storageState: tempDRepAuth, - wallet, - enableDRepSigning: true, - }); - }); - - const verifyVoteWithMetadata = async (testInfo: any, useGovToolIPFS: boolean = false) => { - test.setTimeout(testInfo.timeout + environments.txTimeOut); - - const govActionsPage = new GovernanceActionsPage(dRepPage); - await govActionsPage.goto(); - - const govActionDetailsPage = await govActionsPage.viewFirstProposal(); - const fakerContext = faker.lorem.sentence(200); - - if (useGovToolIPFS) { - await govActionDetailsPage.vote(fakerContext, false, true); - } else { - await govActionDetailsPage.vote(fakerContext); - } - - await dRepPage.reload(); - await dRepPage.waitForTimeout(5_000); - await govActionsPage.votedTab.click(); - - const votedGovActionDetailsPage = await govActionsPage.viewFirstVotedProposal(); - await votedGovActionDetailsPage.currentPage.getByTestId("show-more-button").click(); - await votedGovActionDetailsPage.currentPage.waitForTimeout(2000); - - const voteRationaleContext = await votedGovActionDetailsPage.currentPage.getByTestId("vote-rationale-context"); - await expect(voteRationaleContext).toContainText(fakerContext); - }; - - test("5M_1. Should vote with Context (Download and store yourself)", async ({}, testInfo) => { - await verifyVoteWithMetadata(testInfo, false); - }); - - test("5M_2. Should vote with Context (GovTool pins data to IPFS)", async ({}, testInfo) => { - await verifyVoteWithMetadata(testInfo, true); - }); -}); \ No newline at end of file From 0cca71c7cbc437f806110ffe046622d96b5fd9a1 Mon Sep 17 00:00:00 2001 From: Adam Tomaszczyk Date: Wed, 27 Aug 2025 16:04:23 +0200 Subject: [PATCH 20/22] More DRep search improvements #4030 --- govtool/backend/sql/list-dreps.sql | 13 ++ govtool/backend/src/VVA/API.hs | 3 + govtool/backend/src/VVA/API/Types.hs | 4 +- govtool/backend/src/VVA/DRep.hs | 4 +- govtool/backend/src/VVA/Types.hs | 2 + .../components/molecules/PaginationFooter.tsx | 137 ++++++++++++++++++ .../src/consts/dRepDirectory/sorting.ts | 4 +- .../frontend/src/context/contextProviders.tsx | 13 +- .../frontend/src/context/dataActionsBar.tsx | 2 + govtool/frontend/src/context/index.ts | 1 + govtool/frontend/src/context/pagination.tsx | 56 +++++++ .../queries/useGetDRepListInfiniteQuery.ts | 50 +------ .../src/hooks/queries/useGetDRepListQuery.ts | 46 +++--- govtool/frontend/src/hooks/useUpdateEffect.ts | 16 ++ govtool/frontend/src/models/api.ts | 2 +- .../src/pages/DRepDirectoryContent.tsx | 53 ++++--- 16 files changed, 297 insertions(+), 109 deletions(-) create mode 100644 govtool/frontend/src/components/molecules/PaginationFooter.tsx create mode 100644 govtool/frontend/src/context/pagination.tsx create mode 100644 govtool/frontend/src/hooks/useUpdateEffect.ts diff --git a/govtool/backend/sql/list-dreps.sql b/govtool/backend/sql/list-dreps.sql index 0d7be1697..5ba8e3250 100644 --- a/govtool/backend/sql/list-dreps.sql +++ b/govtool/backend/sql/list-dreps.sql @@ -32,6 +32,16 @@ LatestVoteEpoch AS ( JOIN tx ON tx.id = lvp.tx_id JOIN block ON block.id = tx.block_id ), +VotesLastYear AS ( + SELECT + vp.drep_voter AS drep_id, + COUNT(DISTINCT vp.gov_action_proposal_id) AS votes_last_year + FROM voting_procedure vp + JOIN tx ON tx.id = vp.tx_id + JOIN block ON block.id = tx.block_id + WHERE block.time >= now() - INTERVAL '1 year' + GROUP BY vp.drep_voter +), RankedDRepRegistration AS ( SELECT DISTINCT ON (dr.drep_hash_id) dr.id, @@ -127,6 +137,7 @@ DRepData AS ( off_chain_vote_drep_data.qualifications, off_chain_vote_drep_data.image_url, off_chain_vote_drep_data.image_hash, + COALESCE(vly.votes_last_year, 0) AS votes_last_year, COALESCE( ( SELECT jsonb_agg( @@ -239,6 +250,7 @@ DRepData AS ( LEFT JOIN tx AS tx_first_register ON tx_first_register.id = dr_first_register.tx_id LEFT JOIN block AS block_first_register ON block_first_register.id = tx_first_register.block_id LEFT JOIN LatestVoteEpoch lve ON lve.drep_id = dh.id + LEFT JOIN VotesLastYear vly ON vly.drep_id = dh.id CROSS JOIN DRepActivity GROUP BY dh.raw, @@ -265,6 +277,7 @@ DRepData AS ( off_chain_vote_drep_data.qualifications, off_chain_vote_drep_data.image_url, off_chain_vote_drep_data.image_hash, + vly.votes_last_year, ( SELECT jsonb_agg( jsonb_build_object( diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index 5185c3882..de95e6301 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -160,6 +160,7 @@ drepRegistrationToDrep Types.DRepRegistration {..} = dRepQualifications = dRepRegistrationQualifications, dRepImageUrl = dRepRegistrationImageUrl, dRepImageHash = HexText <$> dRepRegistrationImageHash, + dRepVotesLastYear = dRepRegistrationVotesLastYear, dRepIdentityReferences = DRepReferences <$> dRepRegistrationIdentityReferences, dRepLinkReferences = DRepReferences <$> dRepRegistrationLinkReferences } @@ -205,6 +206,8 @@ drepList mSearchQuery statuses mSortMode mPage mPageSize = do Just Random -> fmap snd . sortOn fst . Prelude.zip randomizedOrderList Just VotingPower -> sortOn $ \Types.DRepRegistration {..} -> Down dRepRegistrationVotingPower + Just Activity -> sortOn $ \Types.DRepRegistration {..} -> + Down dRepRegistrationVotesLastYear Just RegistrationDate -> sortOn $ \Types.DRepRegistration {..} -> Down dRepRegistrationLatestRegistrationDate Just Status -> sortOn $ \Types.DRepRegistration {..} -> diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index 2b1badf27..e0afad810 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -205,7 +205,7 @@ instance ToParamSchema GovernanceActionType where & enum_ ?~ map toJSON (enumFromTo minBound maxBound :: [GovernanceActionType]) -data DRepSortMode = Random | VotingPower | RegistrationDate | Status deriving +data DRepSortMode = Random | VotingPower | Activity | RegistrationDate | Status deriving ( Bounded , Enum , Eq @@ -917,6 +917,7 @@ data DRep , dRepQualifications :: Maybe Text , dRepImageUrl :: Maybe Text , dRepImageHash :: Maybe HexText + , dRepVotesLastYear :: Maybe Integer , dRepIdentityReferences :: Maybe DRepReferences , dRepLinkReferences :: Maybe DRepReferences } @@ -944,6 +945,7 @@ exampleDrep = <> "\"qualifications\": \"Some Qualifications\"," <> "\"qualifications\": \"Some Qualifications\"," <> "\"imageUrl\": \"https://image.url\"," + <> "\"votesLastYear\": 15," <> "\"imageHash\": \"9198b1b204273ba5c67a13310b5a806034160f6a063768297e161d9b759cad61\"}" -- ToSchema instance for DRep diff --git a/govtool/backend/src/VVA/DRep.hs b/govtool/backend/src/VVA/DRep.hs index 8e6f7ceae..44b172f7f 100644 --- a/govtool/backend/src/VVA/DRep.hs +++ b/govtool/backend/src/VVA/DRep.hs @@ -59,6 +59,7 @@ data DRepQueryResult , queryQualifications :: Maybe Text , queryImageUrl :: Maybe Text , queryImageHash :: Maybe Text + , queryVotesLastYear :: Maybe Integer , queryIdentityReferences :: Maybe Value , queryLinkReferences :: Maybe Value } @@ -69,7 +70,7 @@ instance FromRow DRepQueryResult where <$> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field - <*> field <*> field <*> field <*> field + <*> field <*> field <*> field <*> field <*> field sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs @@ -113,6 +114,7 @@ listDReps mSearchQuery = withPool $ \conn -> do (queryQualifications result) (queryImageUrl result) (queryImageHash result) + (queryVotesLastYear result) (queryIdentityReferences result) (queryLinkReferences result) | result <- results diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index 98fb7b44e..9ace0e5bc 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -160,6 +160,7 @@ data DRepRegistration , dRepRegistrationQualifications :: Maybe Text , dRepRegistrationImageUrl :: Maybe Text , dRepRegistrationImageHash :: Maybe Text + , dRepRegistrationVotesLastYear :: Maybe Integer , dRepRegistrationIdentityReferences :: Maybe Value , dRepRegistrationLinkReferences :: Maybe Value } @@ -187,6 +188,7 @@ instance FromRow DRepRegistration where <*> field -- dRepRegistrationQualifications <*> field -- dRepRegistrationImageUrl <*> field -- dRepRegistrationImageHash + <*> field -- dRepRegistrationVotesLastYear <*> field -- dRepRegistrationIdentityReferences <*> field -- dRepRegistrationLinkReferences diff --git a/govtool/frontend/src/components/molecules/PaginationFooter.tsx b/govtool/frontend/src/components/molecules/PaginationFooter.tsx new file mode 100644 index 000000000..77a751105 --- /dev/null +++ b/govtool/frontend/src/components/molecules/PaginationFooter.tsx @@ -0,0 +1,137 @@ +import { + Box, + Typography, + IconButton, + Select, + MenuItem, + SelectChangeEvent, +} from "@mui/material"; +import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft"; +import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight"; +import { FC } from "react"; + +type Props = { + page: number; + total: number; + pageSize: number; + onPageChange: (nextPage: number) => void; + onPageSizeChange: (nextRpp: number) => void; + pageSizeOptions?: number[]; +}; + +export const PaginationFooter: FC = ({ + page, + total, + pageSize, + onPageChange, + onPageSizeChange, + pageSizeOptions = [5, 10, 25, 50], +}) => { + const pageCount = Math.max(1, Math.ceil((total || 0) / (pageSize || 1))); + const clampedPage = Math.min(Math.max(page, 1), pageCount); + + const start = total === 0 ? 0 : (clampedPage - 1) * pageSize + 1; + const end = total === 0 ? 0 : Math.min(clampedPage * pageSize, total); + + const handlePrev = () => onPageChange(Math.max(clampedPage - 1, 1)); + const handleNext = () => onPageChange(Math.min(clampedPage + 1, pageCount)); + + const handlePageSizeChange = (e: SelectChangeEvent) => { + const next = Number(e.target.value); + onPageSizeChange(next); + + const nextPageCount = Math.max(1, Math.ceil((total || 0) / next)); + if (clampedPage > nextPageCount) { + onPageChange(nextPageCount); + } + }; + + return ( + + + + Rows per page: + + + + + + + {start}-{end} of {total} + + + + + + + + + {clampedPage} + + + = pageCount || total === 0} + aria-label="Next page" + > + + + + + ); +}; diff --git a/govtool/frontend/src/consts/dRepDirectory/sorting.ts b/govtool/frontend/src/consts/dRepDirectory/sorting.ts index 0e6c4d016..2ae238b15 100644 --- a/govtool/frontend/src/consts/dRepDirectory/sorting.ts +++ b/govtool/frontend/src/consts/dRepDirectory/sorting.ts @@ -1,7 +1,7 @@ export const DREP_DIRECTORY_SORTING = [ { - key: "Random", - label: "Random", + key: "Activity", + label: "Activity", }, { key: "RegistrationDate", diff --git a/govtool/frontend/src/context/contextProviders.tsx b/govtool/frontend/src/context/contextProviders.tsx index f456018bb..7337418b1 100644 --- a/govtool/frontend/src/context/contextProviders.tsx +++ b/govtool/frontend/src/context/contextProviders.tsx @@ -3,6 +3,7 @@ import { CardanoProvider, useCardano } from "./wallet"; import { ModalProvider, useModal } from "./modal"; import { SnackbarProvider, useSnackbar } from "./snackbar"; import { DataActionsBarProvider } from "./dataActionsBar"; +import { PaginationProvider } from "./pagination"; import { FeatureFlagProvider } from "./featureFlag"; import { GovernanceActionProvider } from "./governanceAction"; import { AdaHandleProvider } from "./adaHandle"; @@ -23,11 +24,13 @@ const ContextProviders = ({ children }: Props) => ( - - - {children} - - + + + + {children} + + + diff --git a/govtool/frontend/src/context/dataActionsBar.tsx b/govtool/frontend/src/context/dataActionsBar.tsx index e2776346c..2bf291edd 100644 --- a/govtool/frontend/src/context/dataActionsBar.tsx +++ b/govtool/frontend/src/context/dataActionsBar.tsx @@ -24,6 +24,7 @@ interface DataActionsBarContextType { debouncedSearchText: string; filtersOpen: boolean; searchText: string; + lastPath: string; setChosenFilters: Dispatch>; setChosenSorting: Dispatch>; setFiltersOpen: Dispatch>; @@ -120,6 +121,7 @@ const DataActionsBarProvider: FC = ({ children }) => { debouncedSearchText, filtersOpen, searchText, + lastPath, setChosenFilters, setChosenSorting, setFiltersOpen, diff --git a/govtool/frontend/src/context/index.ts b/govtool/frontend/src/context/index.ts index 0ece5a1b5..6914a1289 100644 --- a/govtool/frontend/src/context/index.ts +++ b/govtool/frontend/src/context/index.ts @@ -1,6 +1,7 @@ export * from "./appContext"; export * from "./contextProviders"; export * from "./dataActionsBar"; +export * from "./pagination"; export * from "./modal"; export * from "./pendingTransaction"; export * from "./snackbar"; diff --git a/govtool/frontend/src/context/pagination.tsx b/govtool/frontend/src/context/pagination.tsx new file mode 100644 index 000000000..f979ec00a --- /dev/null +++ b/govtool/frontend/src/context/pagination.tsx @@ -0,0 +1,56 @@ +import React, { + createContext, + useContext, + Dispatch, + SetStateAction, + FC, + useState, + useMemo, +} from "react"; + +type PaginationContextType = { + page: number; + pageSize: number; + setPage: Dispatch>; + setPageSize: Dispatch>; +}; + +const PaginationContext = createContext< + PaginationContextType | undefined>( + undefined); +PaginationContext.displayName = "PaginationContext"; + +type PaginationProviderProps = { + children: React.ReactNode; +}; + +const PaginationProvider: FC = ({ children }) => { + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(5); + + const contextValue = useMemo( + () => ({ + page, + pageSize, + setPage, + setPageSize, + }), + [page, pageSize], + ); + + return ( + + {children} + + ); +}; + +function usePagination() { + const ctx = useContext(PaginationContext); + if (!ctx) { + throw new Error("usePagination must be used within a PaginationProvider"); + } + return ctx; +} + +export { PaginationProvider, usePagination }; diff --git a/govtool/frontend/src/hooks/queries/useGetDRepListInfiniteQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepListInfiniteQuery.ts index 2465d9926..51d6930e4 100644 --- a/govtool/frontend/src/hooks/queries/useGetDRepListInfiniteQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDRepListInfiniteQuery.ts @@ -1,18 +1,10 @@ -import { - UseInfiniteQueryOptions, - useInfiniteQuery, - useQuery, -} from "react-query"; -import { useRef, useMemo } from "react"; +import { UseInfiniteQueryOptions, useInfiniteQuery } from "react-query"; import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; import { GetDRepListArguments, getDRepList } from "@services"; import { DRepData, Infinite } from "@/models"; -const makeStatusKey = (status?: string[] | undefined) => - (status && status.length ? [...status].sort().join("|") : "__EMPTY__"); - export const useGetDRepListInfiniteQuery = ( { filters = [], @@ -24,8 +16,6 @@ export const useGetDRepListInfiniteQuery = ( options?: UseInfiniteQueryOptions>, ) => { const { pendingTransaction } = useCardano(); - const totalsByStatusRef = useRef>({}); - const statusKey = useMemo(() => makeStatusKey(status), [status]); const { data, @@ -65,41 +55,6 @@ export const useGetDRepListInfiniteQuery = ( }, enabled: options?.enabled, keepPreviousData: options?.keepPreviousData, - onSuccess: (pagesData) => { - if (!searchPhrase) { - const firstPage = pagesData.pages?.[0]; - if (firstPage && typeof firstPage.total === "number") { - totalsByStatusRef.current[statusKey] = firstPage.total; - } - } - options?.onSuccess?.(pagesData); - }, - }, - ); - - useQuery( - [QUERY_KEYS.useGetDRepListInfiniteKey, "baseline", statusKey], - async () => { - const resp = await getDRepList({ - page: 0, - pageSize: 1, - filters, - searchPhrase: "", - sorting, - status, - }); - return resp; - }, - { - enabled: - options?.enabled && - searchPhrase !== "" && - totalsByStatusRef.current[statusKey] === undefined, - onSuccess: (resp) => { - if (typeof resp.total === "number") { - totalsByStatusRef.current[statusKey] = resp.total; - } - }, }, ); @@ -111,8 +66,5 @@ export const useGetDRepListInfiniteQuery = ( isDRepListLoading: isLoading, dRepData: data?.pages.flatMap((page) => page.elements), isPreviousData, - dRepListTotal: data?.pages[0].total, - dRepTotalsByStatus: totalsByStatusRef.current, - dRepBaselineTotalForStatus: totalsByStatusRef.current[statusKey], }; }; diff --git a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts index 5eedc12df..db62f9c09 100644 --- a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts @@ -1,5 +1,5 @@ -import { useMemo, useRef } from "react"; -import { useQuery, UseQueryOptions } from "react-query"; +import { useMemo } from "react"; +import { useQuery, UseQueryOptions, useQueryClient } from "react-query"; import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; @@ -28,10 +28,11 @@ export function useGetDRepListPaginatedQuery( options?: UseQueryOptions>, ): PaginatedResult { const { pendingTransaction } = useCardano(); - const totalsByStatusRef = useRef>({}); + const queryClient = useQueryClient(); + const statusKey = useMemo(() => makeStatusKey(status), [status]); - const queryKey = [ + const listKey = [ QUERY_KEYS.useGetDRepListInfiniteKey, ( pendingTransaction.registerAsDirectVoter || @@ -48,9 +49,14 @@ export function useGetDRepListPaginatedQuery( status?.length ? status : "", ]; + const baselineKey = useMemo( + () => [QUERY_KEYS.useGetDRepListInfiniteKey, "baseline", statusKey], + [statusKey], + ); + const { data, isLoading, isFetching, isPreviousData } = useQuery( - queryKey, - async () => + listKey, + () => getDRepList({ page, pageSize, @@ -64,36 +70,32 @@ export function useGetDRepListPaginatedQuery( enabled: options?.enabled, onSuccess: (resp) => { if (!searchPhrase && typeof resp?.total === "number") { - totalsByStatusRef.current[statusKey] = resp.total; + queryClient.setQueryData(baselineKey, resp); } options?.onSuccess?.(resp); }, }, ); - useQuery( - [QUERY_KEYS.useGetDRepListInfiniteKey, "baseline", statusKey], - async () => { - const resp = await getDRepList({ + const { data: baselineResp } = useQuery( + baselineKey, + async () => + getDRepList({ page: 0, pageSize: 1, filters, searchPhrase: "", sorting, status, - }); - return resp; - }, + }), { + initialData: () => + queryClient.getQueryData>(baselineKey), enabled: options?.enabled && - searchPhrase !== "" && - totalsByStatusRef.current[statusKey] === undefined, - onSuccess: (resp) => { - if (typeof resp.total === "number") { - totalsByStatusRef.current[statusKey] = resp.total; - } - }, + !queryClient.getQueryData(baselineKey) && + searchPhrase !== "", + staleTime: Infinity, }, ); @@ -103,6 +105,6 @@ export function useGetDRepListPaginatedQuery( isFetching, isPreviousData, total: data?.total, - baselineTotalForStatus: totalsByStatusRef.current[statusKey], + baselineTotalForStatus: baselineResp?.total, }; } diff --git a/govtool/frontend/src/hooks/useUpdateEffect.ts b/govtool/frontend/src/hooks/useUpdateEffect.ts new file mode 100644 index 000000000..192b2a96d --- /dev/null +++ b/govtool/frontend/src/hooks/useUpdateEffect.ts @@ -0,0 +1,16 @@ +import React, { useEffect, useRef } from "react"; + +export function useUpdateEffect( + effect: React.EffectCallback, + deps: React.DependencyList, +) { + const isFirst = useRef(true); + + useEffect(() => { + if (isFirst.current) { + isFirst.current = false; + return; + } + return effect(); + }, deps); +} diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts index 8ba3a6a32..4f48125a6 100644 --- a/govtool/frontend/src/models/api.ts +++ b/govtool/frontend/src/models/api.ts @@ -144,7 +144,7 @@ export enum DRepStatus { } export enum DRepListSort { - Random = "Random", + Activity = "Activity", VotingPower = "VotingPower", RegistrationDate = "RegistrationDate", Status = "Status", diff --git a/govtool/frontend/src/pages/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx index f95ad447c..d66588127 100644 --- a/govtool/frontend/src/pages/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx @@ -1,10 +1,10 @@ import React, { FC, useEffect, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; -import { Box, CircularProgress, Pagination } from "@mui/material"; +import { Box, CircularProgress } from "@mui/material"; import { Typography } from "@atoms"; import { DREP_DIRECTORY_FILTERS, DREP_DIRECTORY_SORTING } from "@consts"; -import { useCardano, useDataActionsBar } from "@context"; +import { useCardano, useDataActionsBar, usePagination } from "@context"; import { useDelegateTodRep, useGetAdaHolderCurrentDelegationQuery, @@ -26,6 +26,8 @@ import { AutomatedVotingOptionDelegationId, } from "@/types/automatedVotingOptions"; import usePrevious from "@/hooks/usePrevious"; +import { PaginationFooter } from "@/components/molecules/PaginationFooter"; +import { useUpdateEffect } from "@/hooks/useUpdateEffect"; interface DRepDirectoryContentProps { isConnected?: boolean; @@ -55,31 +57,31 @@ export const DRepDirectoryContent: FC = ({ searchText, debouncedSearchText, setSearchText, + lastPath, ...dataActionsBarProps } = useDataActionsBar(); + const { page, pageSize, setPage, setPageSize } = usePagination(); + const { chosenFilters, chosenSorting, setChosenFilters, setChosenSorting } = dataActionsBarProps; const [inProgressDelegationDRepData, setInProgressDelegationDRepData] = useState(undefined); - const [page, setPage] = useState(1); - const pageSize = 10; - // Set initial filters and sort useEffect(() => { - // TODO: it should be done only if last page URL WASN'T like /drep_directory/drep1.* - setChosenFilters([DRepStatus.Active]); - setSearchText(""); // <--- Clear the search field on mount + if (!lastPath.includes("drep_directory")) { + setChosenFilters([DRepStatus.Active]); + setSearchText(""); + } }, []); useEffect(() => { - if (!chosenSorting) setChosenSorting(DRepListSort.Random); + if (!chosenSorting) setChosenSorting(DRepListSort.Activity); }, [chosenSorting, setChosenSorting]); - useEffect(() => { - // TODO: it should be done only if last page URL WASN'T like /drep_directory/drep1.* + useUpdateEffect(() => { setPage(1); }, [debouncedSearchText, chosenSorting, JSON.stringify(chosenFilters)]); @@ -142,9 +144,6 @@ export const DRepDirectoryContent: FC = ({ "view", ); - const totalForPaging = typeof total === "number" ? total : 0; - const pageCount = Math.max(1, Math.ceil(totalForPaging / pageSize)); - const isAnAutomatedVotingOptionChosen = currentDelegation?.dRepView && (currentDelegation?.dRepView === @@ -236,9 +235,10 @@ export const DRepDirectoryContent: FC = ({ 1 ? "s" : "", total: baselineTotalForStatus ?? "", }} components={{ @@ -288,19 +288,16 @@ export const DRepDirectoryContent: FC = ({ ))} - {pageCount > 1 && ( - - setPage(newPage)} - shape="rounded" - variant="outlined" - siblingCount={1} - boundaryCount={1} - /> - - )} + { + setPageSize(n); + setPage(1); + }} + /> ); From 3ae9dab4d82bb53910ee7a6ab88df7d5e6618d8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 08:40:46 +0000 Subject: [PATCH 21/22] chore: update @intersect.mbo/govtool-outcomes-pillar-ui to v1.5.7 --- govtool/frontend/package-lock.json | 8 ++++---- govtool/frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/govtool/frontend/package-lock.json b/govtool/frontend/package-lock.json index 3d9f85152..0d75d1dca 100644 --- a/govtool/frontend/package-lock.json +++ b/govtool/frontend/package-lock.json @@ -13,7 +13,7 @@ "@emotion/styled": "^11.11.0", "@emurgo/cardano-serialization-lib-asmjs": "^14.1.1", "@hookform/resolvers": "^3.3.1", - "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.5.6", + "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.5.7", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", "@intersect.mbo/pdf-ui": "1.0.13-beta", "@mui/icons-material": "^5.14.3", @@ -3392,9 +3392,9 @@ } }, "node_modules/@intersect.mbo/govtool-outcomes-pillar-ui": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/@intersect.mbo/govtool-outcomes-pillar-ui/-/govtool-outcomes-pillar-ui-1.5.6.tgz", - "integrity": "sha512-bRo58lf/amigBS1Jp3xhAs1IH8HsSRFCGBahBKl1avon+OIowKn9LgStqoQemfdFDq6HSb9oWJPd5p21SjxLew==", + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/@intersect.mbo/govtool-outcomes-pillar-ui/-/govtool-outcomes-pillar-ui-1.5.7.tgz", + "integrity": "sha512-GGJCDTkwOb4T1DHZ0lQ2RzNL/BoY3y/+wkrpLIoJLtMaoVsa5i41JSx250By8gvVfmmpaLcnwhShQNkJsPRG1w==", "license": "ISC", "dependencies": { "@fontsource/poppins": "^5.0.14", diff --git a/govtool/frontend/package.json b/govtool/frontend/package.json index dcc5d13b8..ad031c36c 100644 --- a/govtool/frontend/package.json +++ b/govtool/frontend/package.json @@ -27,7 +27,7 @@ "@emotion/styled": "^11.11.0", "@emurgo/cardano-serialization-lib-asmjs": "^14.1.1", "@hookform/resolvers": "^3.3.1", - "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.5.6", + "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.5.7", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", "@intersect.mbo/pdf-ui": "1.0.13-beta", "@mui/icons-material": "^5.14.3", From 3fddbc6a1d7f449dc8dc184a9fba27bdb1bb64cb Mon Sep 17 00:00:00 2001 From: Adam Tomaszczyk Date: Thu, 28 Aug 2025 13:47:07 +0200 Subject: [PATCH 22/22] Even more DRep search improvements #4030 --- govtool/backend/app/Main.hs | 3 +- govtool/backend/example-config.json | 1 + govtool/backend/src/VVA/Config.hs | 34 +++++++++++-------- .../components/molecules/PaginationFooter.tsx | 23 ++++++++++++- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/govtool/backend/app/Main.hs b/govtool/backend/app/Main.hs index 4ba525ad0..522b40bb6 100644 --- a/govtool/backend/app/Main.hs +++ b/govtool/backend/app/Main.hs @@ -115,6 +115,7 @@ startApp vvaConfig sentryService = do $ setOnException (exceptionHandler vvaConfig sentryService) defaultSettings cacheEnv <- do let newCache = Cache.newCache (Just $ TimeSpec (fromIntegral (cacheDurationSeconds vvaConfig)) 0) + let newDRepListCache = Cache.newCache (Just $ TimeSpec (fromIntegral (dRepListCacheDurationSeconds vvaConfig)) 0) proposalListCache <- newCache getProposalCache <- newCache currentEpochCache <- newCache @@ -123,7 +124,7 @@ startApp vvaConfig sentryService = do dRepGetVotesCache <- newCache dRepInfoCache <- newCache dRepVotingPowerCache <- newCache - dRepListCache <- newCache + dRepListCache <- newDRepListCache networkMetricsCache <- newCache networkInfoCache <- newCache networkTotalStakeCache <- newCache diff --git a/govtool/backend/example-config.json b/govtool/backend/example-config.json index 800f7dde1..7cfb4d5f2 100644 --- a/govtool/backend/example-config.json +++ b/govtool/backend/example-config.json @@ -10,6 +10,7 @@ "port" : 9999, "host" : "localhost", "cachedurationseconds": 20, + "dreplistcachedurationseconds": 600, "sentrydsn": "https://username:password@senty.host/id", "sentryenv": "dev" } diff --git a/govtool/backend/src/VVA/Config.hs b/govtool/backend/src/VVA/Config.hs index 997a3c231..b11358f3d 100644 --- a/govtool/backend/src/VVA/Config.hs +++ b/govtool/backend/src/VVA/Config.hs @@ -68,19 +68,21 @@ instance DefaultConfig DBConfig where data VVAConfigInternal = VVAConfigInternal { -- | db-sync database access. - vVAConfigInternalDbsyncconfig :: DBConfig + vVAConfigInternalDbsyncconfig :: DBConfig -- | Server port. - , vVAConfigInternalPort :: Int + , vVAConfigInternalPort :: Int -- | Server host. - , vVAConfigInternalHost :: Text + , vVAConfigInternalHost :: Text -- | Request cache duration - , vVaConfigInternalCacheDurationSeconds :: Int + , vVaConfigInternalCacheDurationSeconds :: Int + -- | DRep List request cache duration + , vVaConfigInternalDRepListCacheDurationSeconds :: Int -- | Sentry DSN - , vVAConfigInternalSentrydsn :: String + , vVAConfigInternalSentrydsn :: String -- | Sentry environment - , vVAConfigInternalSentryEnv :: String + , vVAConfigInternalSentryEnv :: String -- | Pinata API JWT - , vVAConfigInternalPinataApiJwt :: Maybe Text + , vVAConfigInternalPinataApiJwt :: Maybe Text } deriving (FromConfig, Generic, Show) @@ -92,6 +94,7 @@ instance DefaultConfig VVAConfigInternal where vVAConfigInternalPort = 3000, vVAConfigInternalHost = "localhost", vVaConfigInternalCacheDurationSeconds = 20, + vVaConfigInternalDRepListCacheDurationSeconds = 600, vVAConfigInternalSentrydsn = "https://username:password@senty.host/id", vVAConfigInternalSentryEnv = "development", vVAConfigInternalPinataApiJwt = Nothing @@ -101,19 +104,21 @@ instance DefaultConfig VVAConfigInternal where data VVAConfig = VVAConfig { -- | db-sync database credentials. - dbSyncConnectionString :: Text + dbSyncConnectionString :: Text -- | Server port. - , serverPort :: Int + , serverPort :: Int -- | Server host. - , serverHost :: Text + , serverHost :: Text -- | Request cache duration - , cacheDurationSeconds :: Int + , cacheDurationSeconds :: Int + -- | DRep List request cache duration + , dRepListCacheDurationSeconds :: Int -- | Sentry DSN - , sentryDSN :: String + , sentryDSN :: String -- | Sentry environment - , sentryEnv :: String + , sentryEnv :: String -- | Pinata API JWT - , pinataApiJwt :: Maybe Text + , pinataApiJwt :: Maybe Text } deriving (Generic, Show, ToJSON) @@ -153,6 +158,7 @@ convertConfig VVAConfigInternal {..} = serverPort = vVAConfigInternalPort, serverHost = vVAConfigInternalHost, cacheDurationSeconds = vVaConfigInternalCacheDurationSeconds, + dRepListCacheDurationSeconds = vVaConfigInternalDRepListCacheDurationSeconds, sentryDSN = vVAConfigInternalSentrydsn, sentryEnv = vVAConfigInternalSentryEnv, pinataApiJwt = vVAConfigInternalPinataApiJwt diff --git a/govtool/frontend/src/components/molecules/PaginationFooter.tsx b/govtool/frontend/src/components/molecules/PaginationFooter.tsx index 77a751105..55f66cabf 100644 --- a/govtool/frontend/src/components/molecules/PaginationFooter.tsx +++ b/govtool/frontend/src/components/molecules/PaginationFooter.tsx @@ -8,7 +8,8 @@ import { } from "@mui/material"; import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft"; import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight"; -import { FC } from "react"; +import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; +import { FC, useState } from "react"; type Props = { page: number; @@ -46,6 +47,19 @@ export const PaginationFooter: FC = ({ } }; + const [open, setOpen] = useState(false); + + const ArrowIcon: React.FC> = (props,) => ( + ) => { + e.preventDefault(); + e.stopPropagation(); + setOpen(true); + }} + /> + ); + return ( = ({ onChange={handlePageSizeChange} variant="standard" disableUnderline + open={open} + onOpen={() => setOpen(true)} + onClose={() => setOpen(false)} + IconComponent={ArrowIcon} sx={{ verticalAlign: "baseline", "& .MuiSelect-select": { py: 0, + pr: "8px !important", lineHeight: 1.5, display: "inline-flex", alignItems: "center", }, "& .MuiSelect-icon": { + pointerEvents: "auto", + cursor: "pointer", top: "calc(50% - 12px)", }, }}