From 11b27e2a32ab391b1993ac9df429aabc25004dd6 Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Sun, 1 Dec 2024 14:14:50 +0000 Subject: [PATCH 1/9] show modal with impacted proposals when changing voting duration --- .../treasury-devdao.near/widget/lib/modal.jsx | 108 +++++++++++++++ .../pages/settings/VotingDurationPage.jsx | 126 ++++++++++++------ .../tests/settings/voting-duration.spec.js | 16 ++- 3 files changed, 205 insertions(+), 45 deletions(-) create mode 100644 instances/treasury-devdao.near/widget/lib/modal.jsx diff --git a/instances/treasury-devdao.near/widget/lib/modal.jsx b/instances/treasury-devdao.near/widget/lib/modal.jsx new file mode 100644 index 00000000..145f9a88 --- /dev/null +++ b/instances/treasury-devdao.near/widget/lib/modal.jsx @@ -0,0 +1,108 @@ +const Modal = styled.div` + display: ${({ hidden }) => (hidden ? "none" : "flex")}; + position: fixed; + inset: 0; + justify-content: center; + align-items: center; + opacity: 1; + z-index: 999; + + .black-btn { + background-color: #000 !important; + border: none; + color: white; + &:active { + color: white; + } + } + + @media screen and (max-width: 768px) { + h5 { + font-size: 16px !important; + } + } + + .btn { + font-size: 14px; + } + + .theme-btn { + background: var(--theme-color) !important; + color: white; + } +`; + +const ModalBackdrop = styled.div` + position: absolute; + inset: 0; + background-color: rgba(0, 0, 0, 0.5); + opacity: 0.4; +`; + +const ModalDialog = styled.div` + padding: 2em; + z-index: 999; + overflow-y: auto; + max-height: 85%; + margin-top: 5%; + width: 35%; + + @media screen and (max-width: 768px) { + margin: 2rem; + width: 100%; + } +`; + +const ModalHeader = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding-bottom: 4px; +`; + +const ModalFooter = styled.div` + padding-top: 4px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: items-center; +`; + +const CloseButton = styled.button` + display: flex; + align-items: center; + justify-content: center; + background-color: white; + padding: 0.5em; + border-radius: 6px; + border: 0; + color: #344054; + + &:hover { + background-color: #d3d3d3; + } +`; + +const ModalContent = styled.div` + flex: 1; + font-size: 14px; + margin-top: 4px; + margin-bottom: 4px; + overflow-y: auto; + max-height: 50%; + text-align: left !important; + @media screen and (max-width: 768px) { + font-size: 12px !important; + } +`; + +const NoButton = styled.button` + background: transparent; + border: none; + padding: 0; + margin: 0; + box-shadow: none; +`; + +return { Modal, ModalBackdrop, ModalContent, ModalDialog, ModalHeader }; diff --git a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx index d7c289d2..02dc63c6 100644 --- a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx +++ b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx @@ -4,6 +4,8 @@ if (!instance) { } const { treasuryDaoID } = VM.require(`${instance}/widget/config.data`); +const { Modal, ModalBackdrop, ModalContent, ModalDialog, ModalHeader } = + VM.require("${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/lib.modal"); const daoPolicy = Near.view(treasuryDaoID, "get_policy", {}); @@ -28,6 +30,8 @@ const [durationDays, setDurationDays] = useState(currentDurationDays); const [proposalsThatWillExpire, setProposalsThatWillExpire] = useState([]); const [showToastStatus, setToastStatus] = useState(null); const [isSubmittingChangeRequest, setSubmittingChangeRequest] = useState(false); +const [showProposalExpiryChangeModal, setShowProposalExpiryChangeModal] = + useState(false); const Container = styled.div` font-size: 14px; @@ -117,10 +121,17 @@ const ToastContainer = styled.div` `; const cancelChangeRequest = () => { + setShowProposalExpiryChangeModal(false); setDurationDays(currentDurationDays); }; const submitChangeRequest = () => { + if (!showProposalExpiryChangeModal && proposalsThatWillExpire.length > 0) { + setShowProposalExpiryChangeModal(true); + return; + } + + setShowProposalExpiryChangeModal(false); setSubmittingChangeRequest(true); Near.call({ contractName: treasuryDaoID, @@ -241,47 +252,80 @@ return (

- {proposalsThatWillExpire.length > 0 ? ( -

-

- - - - - - - - - - - - {proposalsThatWillExpire.map((proposal) => ( - - - - - - - - ))} - -
IdDescriptionSubmission dateCurrent expiryNew expiry
{proposal.id}{proposal.description} - {new Date(proposal.submissionTimeMillis) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} - - {new Date(proposal.currentExpiryTime) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} - - {new Date(proposal.newExpiryTime) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} -
-

+ {showProposalExpiryChangeModal ? ( + + + + +
+ Changing the voting duration will affect the status of some + requests +
+
+ +

+ You are about to update the voting duration. This will affect + the following existing requests. +

+ + + + + + + + + + + + {proposalsThatWillExpire.map((proposal) => ( + + + + + + + + ))} + +
IdDescriptionSubmission dateCurrent expiryNew expiry
{proposal.id}{proposal.description} + {new Date(proposal.submissionTimeMillis) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + {new Date(proposal.currentExpiryTime) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + {new Date(proposal.newExpiryTime) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} +
+
+
+ + +
+
+
) : ( "" )} diff --git a/playwright-tests/tests/settings/voting-duration.spec.js b/playwright-tests/tests/settings/voting-duration.spec.js index c4a255af..a401a5e0 100644 --- a/playwright-tests/tests/settings/voting-duration.spec.js +++ b/playwright-tests/tests/settings/voting-duration.spec.js @@ -284,7 +284,13 @@ test.describe("admin connected", function () { .getByPlaceholder("Enter voting duration days") .fill(newDurationDays.toString()); + await page.waitForTimeout(300); + await page.getByRole("button", { name: "Submit Request" }).click(); + const checkExpectedNewExpiredProposals = async () => { + await expect( + page.getByRole("heading", { name: "Changing the voting duration" }) + ).toBeVisible(); const expectedNewExpiredProposals = proposals .filter( (proposal) => @@ -297,10 +303,6 @@ test.describe("admin connected", function () { proposal.status === "InProgress" ) .reverse(); - await expect(await page.locator(".alert-danger")).toBeVisible(); - await expect(await page.locator(".alert-danger")).toHaveText( - "The following proposals will expire because of the changed duration" - ); await expect( await page.locator(".proposal-that-will-expire") ).toHaveCount(expectedNewExpiredProposals.length); @@ -310,6 +312,12 @@ test.describe("admin connected", function () { expect(visibleProposalIds).toEqual( expectedNewExpiredProposals.map((proposal) => proposal.id.toString()) ); + await page + .locator(".modalfooter button", { hasText: "Cancel" }) + .click(); + await expect( + page.getByRole("heading", { name: "Changing the voting duration" }) + ).not.toBeVisible(); }; await checkExpectedNewExpiredProposals(); await page.waitForTimeout(500); From eb583a2ed53b3603312c1ddaa330c4891f561684 Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Sun, 1 Dec 2024 15:02:53 +0000 Subject: [PATCH 2/9] modal border radius --- instances/treasury-devdao.near/widget/lib/modal.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/instances/treasury-devdao.near/widget/lib/modal.jsx b/instances/treasury-devdao.near/widget/lib/modal.jsx index 145f9a88..75a5ef64 100644 --- a/instances/treasury-devdao.near/widget/lib/modal.jsx +++ b/instances/treasury-devdao.near/widget/lib/modal.jsx @@ -46,6 +46,7 @@ const ModalDialog = styled.div` max-height: 85%; margin-top: 5%; width: 35%; + border-radius: 20px; @media screen and (max-width: 768px) { margin: 2rem; From 5132e29d0ea5f816dcdaf4c0ceb2dd569a6c1e4a Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Sun, 1 Dec 2024 21:27:54 +0000 Subject: [PATCH 3/9] show proposals that will be active if extending voting duration --- .../pages/settings/VotingDurationPage.jsx | 192 +++++++++++++----- .../tests/settings/voting-duration.spec.js | 96 ++++++--- 2 files changed, 210 insertions(+), 78 deletions(-) diff --git a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx index 02dc63c6..16f7b448 100644 --- a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx +++ b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx @@ -28,9 +28,10 @@ const currentDurationDays = const [durationDays, setDurationDays] = useState(currentDurationDays); const [proposalsThatWillExpire, setProposalsThatWillExpire] = useState([]); +const [proposalsThatWillBeActive, setProposalsThatWillBeActive] = useState([]); const [showToastStatus, setToastStatus] = useState(null); const [isSubmittingChangeRequest, setSubmittingChangeRequest] = useState(false); -const [showProposalExpiryChangeModal, setShowProposalExpiryChangeModal] = +const [showAffectedProposalsModal, setShowAffectedProposalsModal] = useState(false); const Container = styled.div` @@ -121,62 +122,15 @@ const ToastContainer = styled.div` `; const cancelChangeRequest = () => { - setShowProposalExpiryChangeModal(false); + setShowAffectedProposalsModal(false); setDurationDays(currentDurationDays); }; -const submitChangeRequest = () => { - if (!showProposalExpiryChangeModal && proposalsThatWillExpire.length > 0) { - setShowProposalExpiryChangeModal(true); - return; - } - - setShowProposalExpiryChangeModal(false); - setSubmittingChangeRequest(true); - Near.call({ - contractName: treasuryDaoID, - methodName: "add_proposal", - deposit, - args: { - proposal: { - description: "Change proposal period", - kind: { - ChangePolicyUpdateParameters: { - parameters: { - proposal_period: - (60 * 60 * 24 * durationDays).toString() + "000000000", - }, - }, - }, - }, - }, - }); -}; - -useEffect(() => { - Near.asyncView(treasuryDaoID, "get_proposal", { - id: lastProposalId - 1, - }).then((proposal) => { - const proposal_period = - proposal?.kind?.ChangePolicyUpdateParameters?.parameters?.proposal_period; - - if ( - proposal_period && - isSubmittingChangeRequest && - Number(proposal_period.substring(0, proposal_period.length - 9)) / - (24 * 60 * 60) === - Number(durationDays) - ) { - setToastStatus(true); - setSubmittingChangeRequest(false); - } - }); -}, [isSubmittingChangeRequest, lastProposalId]); - -const changeDurationDays = (newDurationDays) => { - setDurationDays(newDurationDays); +const findAffectedProposals = (callback) => { + setProposalsThatWillExpire([]); + setProposalsThatWillBeActive([]); const limit = 10; - if (newDurationDays < currentDurationDays) { + if (durationDays < currentDurationDays) { const fetchProposalsThatWillExpire = ( lastIndex, newProposalsThatWillExpire @@ -198,7 +152,7 @@ const changeDurationDays = (newDurationDays) => { const currentExpiryTime = submissionTimeMillis + 24 * 60 * 60 * 1000 * currentDurationDays; const newExpiryTime = - submissionTimeMillis + 24 * 60 * 60 * 1000 * newDurationDays; + submissionTimeMillis + 24 * 60 * 60 * 1000 * durationDays; if ( currentExpiryTime >= now && newExpiryTime < now && @@ -219,13 +173,120 @@ const changeDurationDays = (newDurationDays) => { lastIndex - limit, newProposalsThatWillExpire ); + } else { + callback(newProposalsThatWillExpire.length > 0); } }); }; fetchProposalsThatWillExpire(lastProposalId, []); + } else if (durationDays > currentDurationDays) { + const fetchProposalsThatWillBeActive = ( + lastIndex, + newProposalsThatWillBeActive + ) => { + Near.asyncView(treasuryDaoID, "get_proposals", { + from_index: lastIndex - limit, + limit, + }).then((/** @type Array */ proposals) => { + const now = new Date().getTime(); + + let fetchMore = false; + for (const proposal of proposals.reverse()) { + const submissionTimeMillis = Number( + proposal.submission_time.substr( + 0, + proposal.submission_time.length - 6 + ) + ); + const currentExpiryTime = + submissionTimeMillis + 24 * 60 * 60 * 1000 * currentDurationDays; + const newExpiryTime = + submissionTimeMillis + 24 * 60 * 60 * 1000 * durationDays; + if ( + currentExpiryTime <= now && + newExpiryTime > now && + proposal.status === "InProgress" + ) { + newProposalsThatWillBeActive.push({ + currentExpiryTime, + newExpiryTime, + submissionTimeMillis, + ...proposal, + }); + } + fetchMore = newExpiryTime >= now; + } + setProposalsThatWillBeActive(newProposalsThatWillBeActive); + if (fetchMore) { + fetchProposalsThatWillBeActive( + lastIndex - limit, + newProposalsThatWillBeActive + ); + } else { + callback(newProposalsThatWillBeActive.length > 0); + } + }); + }; + fetchProposalsThatWillBeActive(lastProposalId, []); + } else { + callback(false); } }; +const submitChangeRequest = () => { + findAffectedProposals((shouldShowAffectedProposalsModal) => { + if (!showAffectedProposalsModal && shouldShowAffectedProposalsModal) { + setShowAffectedProposalsModal(true); + return; + } + + setShowAffectedProposalsModal(false); + setSubmittingChangeRequest(true); + Near.call({ + contractName: treasuryDaoID, + methodName: "add_proposal", + deposit, + args: { + proposal: { + description: "Change proposal period", + kind: { + ChangePolicyUpdateParameters: { + parameters: { + proposal_period: + (60 * 60 * 24 * durationDays).toString() + "000000000", + }, + }, + }, + }, + }, + }); + }); +}; + +useEffect(() => { + Near.asyncView(treasuryDaoID, "get_proposal", { + id: lastProposalId - 1, + }).then((proposal) => { + const proposal_period = + proposal?.kind?.ChangePolicyUpdateParameters?.parameters?.proposal_period; + + if ( + proposal_period && + isSubmittingChangeRequest && + Number(proposal_period.substring(0, proposal_period.length - 9)) / + (24 * 60 * 60) === + Number(durationDays) + ) { + setToastStatus(true); + setSubmittingChangeRequest(false); + } + }); +}, [isSubmittingChangeRequest, lastProposalId]); + +const changeDurationDays = (newDurationDays) => { + setDurationDays(newDurationDays); +}; + return (
@@ -252,7 +313,7 @@ return (

- {showProposalExpiryChangeModal ? ( + {showAffectedProposalsModal ? ( @@ -299,6 +360,27 @@ return ( ))} + {proposalsThatWillBeActive.map((proposal) => ( + + {proposal.id} + {proposal.description} + + {new Date(proposal.submissionTimeMillis) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + {new Date(proposal.currentExpiryTime) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + {new Date(proposal.newExpiryTime) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + ))} diff --git a/playwright-tests/tests/settings/voting-duration.spec.js b/playwright-tests/tests/settings/voting-duration.spec.js index a401a5e0..e9a356fe 100644 --- a/playwright-tests/tests/settings/voting-duration.spec.js +++ b/playwright-tests/tests/settings/voting-duration.spec.js @@ -174,7 +174,6 @@ test.describe("admin connected", function () { return new Promise((resolve) => { wallet.signAndSendTransactions = async (transactions) => { - console.log("sign and send tx", transactions); resolve(transactions.transactions[0]); return await new Promise( (transactionSentPromiseResolve) => @@ -247,7 +246,7 @@ test.describe("admin connected", function () { return lastProposalId; } else if (postData.params.method_name === "get_policy") { originalResult.proposal_period = ( - 7n * + 7n * // 7 days 24n * 60n * 60n * @@ -278,7 +277,7 @@ test.describe("admin connected", function () { .getByPlaceholder("Enter voting duration days") .inputValue(); - let newDurationDays = Number(currentDurationDays) - 1; + let newDurationDays = Number(currentDurationDays) + 3; while (newDurationDays > 0) { await page .getByPlaceholder("Enter voting duration days") @@ -287,10 +286,7 @@ test.describe("admin connected", function () { await page.waitForTimeout(300); await page.getByRole("button", { name: "Submit Request" }).click(); - const checkExpectedNewExpiredProposals = async () => { - await expect( - page.getByRole("heading", { name: "Changing the voting duration" }) - ).toBeVisible(); + const checkExpectedNewAffectedProposals = async () => { const expectedNewExpiredProposals = proposals .filter( (proposal) => @@ -303,23 +299,77 @@ test.describe("admin connected", function () { proposal.status === "InProgress" ) .reverse(); - await expect( - await page.locator(".proposal-that-will-expire") - ).toHaveCount(expectedNewExpiredProposals.length); - const visibleProposalIds = await page - .locator(".proposal-that-will-expire td:first-child") - .allInnerTexts(); - expect(visibleProposalIds).toEqual( - expectedNewExpiredProposals.map((proposal) => proposal.id.toString()) - ); - await page - .locator(".modalfooter button", { hasText: "Cancel" }) - .click(); - await expect( - page.getByRole("heading", { name: "Changing the voting duration" }) - ).not.toBeVisible(); + const expectedNewActiveProposals = proposals + .filter( + (proposal) => + Number(BigInt(proposal.submission_time) / 1_000_000n) + + currentDurationDays * 24 * 60 * 60 * 1000 < + new Date().getTime() && + Number(BigInt(proposal.submission_time) / 1_000_000n) + + newDurationDays * 24 * 60 * 60 * 1000 > + new Date().getTime() && + proposal.status === "InProgress" + ) + .reverse(); + if (newDurationDays > currentDurationDays) { + expect(expectedNewActiveProposals.length).toBeGreaterThanOrEqual(0); + expect(expectedNewExpiredProposals.length).toBe(0); + } else { + expect(expectedNewActiveProposals.length).toBe(0); + expect(expectedNewExpiredProposals.length).toBeGreaterThanOrEqual(0); + } + + if (expectedNewExpiredProposals.length > 0) { + await expect( + page.getByRole("heading", { name: "Changing the voting duration" }) + ).toBeVisible(); + await expect( + await page.locator(".proposal-that-will-expire") + ).toHaveCount(expectedNewExpiredProposals.length); + const visibleProposalIds = await page + .locator(".proposal-that-will-expire td:first-child") + .allInnerTexts(); + expect(visibleProposalIds).toEqual( + expectedNewExpiredProposals.map((proposal) => + proposal.id.toString() + ) + ); + await page + .locator(".modalfooter button", { hasText: "Cancel" }) + .click(); + await expect( + page.getByRole("heading", { name: "Changing the voting duration" }) + ).not.toBeVisible(); + } else if (expectedNewActiveProposals.length > 0) { + await expect( + page.getByRole("heading", { name: "Changing the voting duration" }) + ).toBeVisible(); + await expect( + await page.locator(".proposal-that-will-be-active") + ).toHaveCount(expectedNewActiveProposals.length); + const visibleProposalIds = await page + .locator(".proposal-that-will-be-active td:first-child") + .allInnerTexts(); + expect(visibleProposalIds).toEqual( + expectedNewActiveProposals.map((proposal) => proposal.id.toString()) + ); + await page + .locator(".modalfooter button", { hasText: "Cancel" }) + .click(); + await expect( + page.getByRole("heading", { name: "Changing the voting duration" }) + ).not.toBeVisible(); + } else { + await expect( + await page.getByText("Confirm Transaction") + ).toBeVisible(); + await page.locator("button", { hasText: "Close" }).click(); + await expect( + await page.getByText("Confirm Transaction") + ).not.toBeVisible(); + } }; - await checkExpectedNewExpiredProposals(); + await checkExpectedNewAffectedProposals(); await page.waitForTimeout(500); newDurationDays--; } From a0dd677e385411c022db19604b8abbdb9fddc90d Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Mon, 2 Dec 2024 06:43:57 +0000 Subject: [PATCH 4/9] texts about impacted requests --- .../widget/pages/settings/VotingDurationPage.jsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx index 16f7b448..d269f07c 100644 --- a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx +++ b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx @@ -328,6 +328,19 @@ return ( You are about to update the voting duration. This will affect the following existing requests.

+
    + {proposalsThatWillExpire.length > 0 ?
  • + {proposalsThatWillExpire.length} active requests under the old voting duration + will move to the "Archived" tab and close for voting. These requests were + created outside the new voting period and are no longer considered active. +
  • : ""} + {proposalsThatWillBeActive.length > 0 ?
  • + {proposalsThatWillBeActive.length} expired requests under the old voting duration + will move back to the "Pending Requests" tab and reopen for voting. These requests were + created within the new voting period and are no longer considered expired. +
  • : ""} +
  • Impacted requests:
  • +
@@ -383,6 +396,9 @@ return ( ))}
+ {proposalsThatWillBeActive.length > 0 ?

+ If you do not want expired proposals to be open for voting again, you may need to delete them. +

: ""}
Date: Mon, 2 Dec 2024 06:44:33 +0000 Subject: [PATCH 5/9] fmt --- .../pages/settings/VotingDurationPage.jsx | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx index d269f07c..6a91672d 100644 --- a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx +++ b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx @@ -329,17 +329,29 @@ return ( the following existing requests.

    - {proposalsThatWillExpire.length > 0 ?
  • - {proposalsThatWillExpire.length} active requests under the old voting duration - will move to the "Archived" tab and close for voting. These requests were - created outside the new voting period and are no longer considered active. -
  • : ""} - {proposalsThatWillBeActive.length > 0 ?
  • - {proposalsThatWillBeActive.length} expired requests under the old voting duration - will move back to the "Pending Requests" tab and reopen for voting. These requests were - created within the new voting period and are no longer considered expired. -
  • : ""} -
  • Impacted requests:
  • + {proposalsThatWillExpire.length > 0 ? ( +
  • + {proposalsThatWillExpire.length} active requests{" "} + under the old voting duration will move to the "Archived" + tab and close for voting. These requests were created + outside the new voting period and are no longer considered + active. +
  • + ) : ( + "" + )} + {proposalsThatWillBeActive.length > 0 ? ( +
  • + {proposalsThatWillBeActive.length} expired requests{" "} + under the old voting duration will move back to the + "Pending Requests" tab and reopen for voting. These + requests were created within the new voting period and are + no longer considered expired. +
  • + ) : ( + "" + )} +
  • Impacted requests:
@@ -396,9 +408,14 @@ return ( ))}
- {proposalsThatWillBeActive.length > 0 ?

- If you do not want expired proposals to be open for voting again, you may need to delete them. -

: ""} + {proposalsThatWillBeActive.length > 0 ? ( +

+ If you do not want expired proposals to be open for voting + again, you may need to delete them. +

+ ) : ( + "" + )}
Date: Mon, 2 Dec 2024 17:58:48 +0000 Subject: [PATCH 6/9] show pending proposals that will follow the new voting duration ( without expiring or becoming active ) --- .../pages/settings/VotingDurationPage.jsx | 80 +++++++++++++++---- .../tests/settings/voting-duration.spec.js | 48 ++++++++++- 2 files changed, 110 insertions(+), 18 deletions(-) diff --git a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx index 6a91672d..3cb0adc5 100644 --- a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx +++ b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx @@ -29,6 +29,7 @@ const currentDurationDays = const [durationDays, setDurationDays] = useState(currentDurationDays); const [proposalsThatWillExpire, setProposalsThatWillExpire] = useState([]); const [proposalsThatWillBeActive, setProposalsThatWillBeActive] = useState([]); +const [otherPendingRequests, setOtherPendingRequests] = useState([]); const [showToastStatus, setToastStatus] = useState(null); const [isSubmittingChangeRequest, setSubmittingChangeRequest] = useState(false); const [showAffectedProposalsModal, setShowAffectedProposalsModal] = @@ -129,11 +130,13 @@ const cancelChangeRequest = () => { const findAffectedProposals = (callback) => { setProposalsThatWillExpire([]); setProposalsThatWillBeActive([]); + setOtherPendingRequests([]); const limit = 10; if (durationDays < currentDurationDays) { const fetchProposalsThatWillExpire = ( lastIndex, - newProposalsThatWillExpire + newProposalsThatWillExpire, + newOtherPendingRequests ) => { Near.asyncView(treasuryDaoID, "get_proposals", { from_index: lastIndex - limit, @@ -164,25 +167,38 @@ const findAffectedProposals = (callback) => { submissionTimeMillis, ...proposal, }); + } else if (proposal.status === "InProgress" && newExpiryTime > now) { + newOtherPendingRequests.push({ + currentExpiryTime, + newExpiryTime, + submissionTimeMillis, + ...proposal, + }); } fetchMore = currentExpiryTime >= now; } setProposalsThatWillExpire(newProposalsThatWillExpire); + setOtherPendingRequests(newOtherPendingRequests); if (fetchMore) { fetchProposalsThatWillExpire( lastIndex - limit, - newProposalsThatWillExpire + newProposalsThatWillExpire, + newOtherPendingRequests ); } else { - callback(newProposalsThatWillExpire.length > 0); + callback( + newProposalsThatWillExpire.length > 0 || + newOtherPendingRequests.length > 0 + ); } }); }; - fetchProposalsThatWillExpire(lastProposalId, []); + fetchProposalsThatWillExpire(lastProposalId, [], []); } else if (durationDays > currentDurationDays) { const fetchProposalsThatWillBeActive = ( lastIndex, - newProposalsThatWillBeActive + newProposalsThatWillBeActive, + newOtherPendingRequests ) => { Near.asyncView(treasuryDaoID, "get_proposals", { from_index: lastIndex - limit, @@ -213,23 +229,33 @@ const findAffectedProposals = (callback) => { submissionTimeMillis, ...proposal, }); + } else if (proposal.status === "InProgress" && newExpiryTime > now) { + newOtherPendingRequests.push({ + currentExpiryTime, + newExpiryTime, + submissionTimeMillis, + ...proposal, + }); } fetchMore = newExpiryTime >= now; } setProposalsThatWillBeActive(newProposalsThatWillBeActive); + setOtherPendingRequests(newOtherPendingRequests); if (fetchMore) { fetchProposalsThatWillBeActive( lastIndex - limit, - newProposalsThatWillBeActive + newProposalsThatWillBeActive, + newOtherPendingRequests ); } else { - callback(newProposalsThatWillBeActive.length > 0); + callback( + newProposalsThatWillBeActive.length > 0 || + newOtherPendingRequests.length > 0 + ); } }); }; - fetchProposalsThatWillBeActive(lastProposalId, []); - } else { - callback(false); + fetchProposalsThatWillBeActive(lastProposalId, [], []); } }; @@ -329,9 +355,23 @@ return ( the following existing requests.

    + {otherPendingRequests.length > 0 ? ( +
  • + Pending requests: +
    + {otherPendingRequests.length} pending requests will + now follow the new voting duration policy. +
  • + ) : ( + "" + )} {proposalsThatWillExpire.length > 0 ? (
  • - {proposalsThatWillExpire.length} active requests{" "} + Active requests: +
    + + {proposalsThatWillExpire.length} active requests + {" "} under the old voting duration will move to the "Archived" tab and close for voting. These requests were created outside the new voting period and are no longer considered @@ -342,7 +382,11 @@ return ( )} {proposalsThatWillBeActive.length > 0 ? (
  • - {proposalsThatWillBeActive.length} expired requests{" "} + Expired requests: +
    + + {proposalsThatWillBeActive.length} expired requests + {" "} under the old voting duration will move back to the "Pending Requests" tab and reopen for voting. These requests were created within the new voting period and are @@ -445,10 +489,18 @@ return ( "" )} - -
diff --git a/playwright-tests/tests/settings/voting-duration.spec.js b/playwright-tests/tests/settings/voting-duration.spec.js index e9a356fe..13de426a 100644 --- a/playwright-tests/tests/settings/voting-duration.spec.js +++ b/playwright-tests/tests/settings/voting-duration.spec.js @@ -212,6 +212,7 @@ test.describe("admin connected", function () { instanceAccount, daoAccount, }) => { + test.setTimeout(60_000); const lastProposalId = 100; const proposals = []; for (let id = 0; id <= lastProposalId; id++) { @@ -283,9 +284,6 @@ test.describe("admin connected", function () { .getByPlaceholder("Enter voting duration days") .fill(newDurationDays.toString()); - await page.waitForTimeout(300); - await page.getByRole("button", { name: "Submit Request" }).click(); - const checkExpectedNewAffectedProposals = async () => { const expectedNewExpiredProposals = proposals .filter( @@ -311,15 +309,52 @@ test.describe("admin connected", function () { proposal.status === "InProgress" ) .reverse(); + const expectedUnaffectedActiveProposals = proposals + .filter( + (proposal) => + Number(BigInt(proposal.submission_time) / 1_000_000n) + + currentDurationDays * 24 * 60 * 60 * 1000 > + new Date().getTime() && + Number(BigInt(proposal.submission_time) / 1_000_000n) + + newDurationDays * 24 * 60 * 60 * 1000 > + new Date().getTime() && + proposal.status === "InProgress" + ) + .reverse(); if (newDurationDays > currentDurationDays) { expect(expectedNewActiveProposals.length).toBeGreaterThanOrEqual(0); expect(expectedNewExpiredProposals.length).toBe(0); - } else { + } else if (newDurationDays < currentDurationDays) { expect(expectedNewActiveProposals.length).toBe(0); expect(expectedNewExpiredProposals.length).toBeGreaterThanOrEqual(0); + } else { + expect(expectedNewActiveProposals.length).toBe(0); + expect(expectedNewExpiredProposals.length).toBe(0); + await expect( + await page.getByRole("button", { name: "Submit Request" }) + ).toBeDisabled(); + return; } + await page.waitForTimeout(300); + await expect( + await page.getByRole("button", { name: "Submit Request" }) + ).toBeEnabled(); + await page.getByRole("button", { name: "Submit Request" }).click(); + + if (expectedUnaffectedActiveProposals.length > 0) { + await expect( + page.getByText( + `Pending requests: ${expectedUnaffectedActiveProposals.length} pending` + ) + ).toBeVisible(); + } if (expectedNewExpiredProposals.length > 0) { + await expect( + await page.getByText( + `Active requests: ${expectedNewExpiredProposals.length} active` + ) + ).toBeVisible(); await expect( page.getByRole("heading", { name: "Changing the voting duration" }) ).toBeVisible(); @@ -341,6 +376,11 @@ test.describe("admin connected", function () { page.getByRole("heading", { name: "Changing the voting duration" }) ).not.toBeVisible(); } else if (expectedNewActiveProposals.length > 0) { + await expect( + await page.getByText( + `Expired requests: ${expectedNewActiveProposals.length} expired` + ) + ).toBeVisible(); await expect( page.getByRole("heading", { name: "Changing the voting duration" }) ).toBeVisible(); From d0eb623b81422bdd13861bfc9295f75e1fda3e55 Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Mon, 2 Dec 2024 21:05:22 +0000 Subject: [PATCH 7/9] handle if there are fewer proposals than limit ( 10 ) --- .../pages/settings/VotingDurationPage.jsx | 125 +++++++++--------- .../tests/settings/voting-duration.spec.js | 9 ++ 2 files changed, 75 insertions(+), 59 deletions(-) diff --git a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx index 3cb0adc5..0d80e0c8 100644 --- a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx +++ b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx @@ -139,7 +139,7 @@ const findAffectedProposals = (callback) => { newOtherPendingRequests ) => { Near.asyncView(treasuryDaoID, "get_proposals", { - from_index: lastIndex - limit, + from_index: lastIndex < limit ? 0 : lastIndex - limit, limit, }).then((/** @type Array */ proposals) => { const now = new Date().getTime(); @@ -175,7 +175,7 @@ const findAffectedProposals = (callback) => { ...proposal, }); } - fetchMore = currentExpiryTime >= now; + fetchMore = proposals.length === limit && currentExpiryTime >= now; } setProposalsThatWillExpire(newProposalsThatWillExpire); setOtherPendingRequests(newOtherPendingRequests); @@ -201,7 +201,7 @@ const findAffectedProposals = (callback) => { newOtherPendingRequests ) => { Near.asyncView(treasuryDaoID, "get_proposals", { - from_index: lastIndex - limit, + from_index: lastIndex < limit ? 0 : lastIndex - limit, limit, }).then((/** @type Array */ proposals) => { const now = new Date().getTime(); @@ -237,7 +237,7 @@ const findAffectedProposals = (callback) => { ...proposal, }); } - fetchMore = newExpiryTime >= now; + fetchMore = proposals.length === limit && newExpiryTime >= now; } setProposalsThatWillBeActive(newProposalsThatWillBeActive); setOtherPendingRequests(newOtherPendingRequests); @@ -313,6 +313,9 @@ const changeDurationDays = (newDurationDays) => { setDurationDays(newDurationDays); }; +const showImpactedRequests = + proposalsThatWillExpire.length > 0 || proposalsThatWillBeActive.length > 0; + return (
@@ -395,63 +398,67 @@ return ( ) : ( "" )} -
  • Impacted requests:
  • + {showImpactedRequests ?
  • Impacted requests:
  • : ""} - - - - - - - - - - - - {proposalsThatWillExpire.map((proposal) => ( - - - - - - - - ))} - {proposalsThatWillBeActive.map((proposal) => ( - - - - - - + {showImpactedRequests ? ( +
    IdDescriptionSubmission dateCurrent expiryNew expiry
    {proposal.id}{proposal.description} - {new Date(proposal.submissionTimeMillis) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} - - {new Date(proposal.currentExpiryTime) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} - - {new Date(proposal.newExpiryTime) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} -
    {proposal.id}{proposal.description} - {new Date(proposal.submissionTimeMillis) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} - - {new Date(proposal.currentExpiryTime) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} - - {new Date(proposal.newExpiryTime) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} -
    + + + + + + + - ))} - -
    IdDescriptionSubmission dateCurrent expiryNew expiry
    + + + {proposalsThatWillExpire.map((proposal) => ( + + {proposal.id} + {proposal.description} + + {new Date(proposal.submissionTimeMillis) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + {new Date(proposal.currentExpiryTime) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + {new Date(proposal.newExpiryTime) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + ))} + {proposalsThatWillBeActive.map((proposal) => ( + + {proposal.id} + {proposal.description} + + {new Date(proposal.submissionTimeMillis) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + {new Date(proposal.currentExpiryTime) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + {new Date(proposal.newExpiryTime) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + ))} + + + ) : ( + "" + )} {proposalsThatWillBeActive.length > 0 ? (

    If you do not want expired proposals to be open for voting diff --git a/playwright-tests/tests/settings/voting-duration.spec.js b/playwright-tests/tests/settings/voting-duration.spec.js index 13de426a..e3fcee0f 100644 --- a/playwright-tests/tests/settings/voting-duration.spec.js +++ b/playwright-tests/tests/settings/voting-duration.spec.js @@ -65,6 +65,10 @@ test.describe("admin connected", function () { await page.waitForTimeout(500); await page.locator("button", { hasText: "Submit" }).click(); + await page + .locator(".modalfooter button", { hasText: "Yes, proceed" }) + .click(); + await expect(await getTransactionModalObject(page)).toEqual({ proposal: { description: "Change proposal period", @@ -126,6 +130,7 @@ test.describe("admin connected", function () { instanceAccount, daoAccount, }) => { + test.setTimeout(60_000); const daoName = daoAccount.split(".")[0]; const sandbox = new SandboxRPC(); @@ -166,6 +171,10 @@ test.describe("admin connected", function () { await page.waitForTimeout(500); await page.locator("button", { hasText: "Submit" }).click(); + await page + .locator(".modalfooter button", { hasText: "Yes, proceed" }) + .click(); + const transactionToSendPromise = page.evaluate(async () => { const selector = await document.querySelector("near-social-viewer") .selectorPromise; From 68814ff1b01188970e298721397344ae39d1d3aa Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Sun, 8 Dec 2024 15:28:17 +0000 Subject: [PATCH 8/9] adjustments according to review comments --- .../pages/settings/VotingDurationPage.jsx | 130 ++++++++---------- .../tests/settings/voting-duration.spec.js | 14 +- 2 files changed, 68 insertions(+), 76 deletions(-) diff --git a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx index 0d80e0c8..e9262b40 100644 --- a/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx +++ b/instances/treasury-devdao.near/widget/pages/settings/VotingDurationPage.jsx @@ -348,8 +348,8 @@ return (

    - Changing the voting duration will affect the status of some - requests + + Impact of changing voting duration
    @@ -360,8 +360,6 @@ return (
      {otherPendingRequests.length > 0 ? (
    • - Pending requests: -
      {otherPendingRequests.length} pending requests will now follow the new voting duration policy.
    • @@ -370,11 +368,7 @@ return ( )} {proposalsThatWillExpire.length > 0 ? (
    • - Active requests: -
      - - {proposalsThatWillExpire.length} active requests - {" "} + {proposalsThatWillExpire.length} active requests{" "} under the old voting duration will move to the "Archived" tab and close for voting. These requests were created outside the new voting period and are no longer considered @@ -385,11 +379,7 @@ return ( )} {proposalsThatWillBeActive.length > 0 ? (
    • - Expired requests: -
      - - {proposalsThatWillBeActive.length} expired requests - {" "} + {proposalsThatWillBeActive.length} expired requests{" "} under the old voting duration will move back to the "Pending Requests" tab and reopen for voting. These requests were created within the new voting period and are @@ -398,64 +388,66 @@ return ( ) : ( "" )} - {showImpactedRequests ?
    • Impacted requests:
    • : ""}
    {showImpactedRequests ? ( - - - - - - - - - - - - {proposalsThatWillExpire.map((proposal) => ( - - - - - - + <> +

    Summary of changes

    +
    IdDescriptionSubmission dateCurrent expiryNew expiry
    {proposal.id}{proposal.description} - {new Date(proposal.submissionTimeMillis) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} - - {new Date(proposal.currentExpiryTime) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} - - {new Date(proposal.newExpiryTime) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} -
    + + + + + + + - ))} - {proposalsThatWillBeActive.map((proposal) => ( - - - - - - - - ))} - -
    IdDescriptionSubmission dateCurrent expiryNew expiry
    {proposal.id}{proposal.description} - {new Date(proposal.submissionTimeMillis) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} - - {new Date(proposal.currentExpiryTime) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} - - {new Date(proposal.newExpiryTime) - .toJSON() - .substring(0, "yyyy-mm-dd".length)} -
    + + + {proposalsThatWillExpire.map((proposal) => ( + + {proposal.id} + {proposal.description} + + {new Date(proposal.submissionTimeMillis) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + {new Date(proposal.currentExpiryTime) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + {new Date(proposal.newExpiryTime) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + ))} + {proposalsThatWillBeActive.map((proposal) => ( + + {proposal.id} + {proposal.description} + + {new Date(proposal.submissionTimeMillis) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + {new Date(proposal.currentExpiryTime) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + {new Date(proposal.newExpiryTime) + .toJSON() + .substring(0, "yyyy-mm-dd".length)} + + + ))} + + + ) : ( "" )} diff --git a/playwright-tests/tests/settings/voting-duration.spec.js b/playwright-tests/tests/settings/voting-duration.spec.js index e3fcee0f..3c3f844b 100644 --- a/playwright-tests/tests/settings/voting-duration.spec.js +++ b/playwright-tests/tests/settings/voting-duration.spec.js @@ -354,18 +354,18 @@ test.describe("admin connected", function () { if (expectedUnaffectedActiveProposals.length > 0) { await expect( page.getByText( - `Pending requests: ${expectedUnaffectedActiveProposals.length} pending` + `${expectedUnaffectedActiveProposals.length} pending requests` ) ).toBeVisible(); } if (expectedNewExpiredProposals.length > 0) { await expect( await page.getByText( - `Active requests: ${expectedNewExpiredProposals.length} active` + `${expectedNewExpiredProposals.length} active requests` ) ).toBeVisible(); await expect( - page.getByRole("heading", { name: "Changing the voting duration" }) + page.getByRole("heading", { name: "Impact of changing voting duration" }) ).toBeVisible(); await expect( await page.locator(".proposal-that-will-expire") @@ -382,16 +382,16 @@ test.describe("admin connected", function () { .locator(".modalfooter button", { hasText: "Cancel" }) .click(); await expect( - page.getByRole("heading", { name: "Changing the voting duration" }) + page.getByRole("heading", { name: "Impact of changing voting duration" }) ).not.toBeVisible(); } else if (expectedNewActiveProposals.length > 0) { await expect( await page.getByText( - `Expired requests: ${expectedNewActiveProposals.length} expired` + `${expectedNewActiveProposals.length} expired` ) ).toBeVisible(); await expect( - page.getByRole("heading", { name: "Changing the voting duration" }) + page.getByRole("heading", { name: "Impact of changing voting duration" }) ).toBeVisible(); await expect( await page.locator(".proposal-that-will-be-active") @@ -406,7 +406,7 @@ test.describe("admin connected", function () { .locator(".modalfooter button", { hasText: "Cancel" }) .click(); await expect( - page.getByRole("heading", { name: "Changing the voting duration" }) + page.getByRole("heading", { name: "Impact of changing voting duration" }) ).not.toBeVisible(); } else { await expect( From f0b11b34bed79f545e4e22e619975dbb92d4e628 Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Sun, 8 Dec 2024 15:34:26 +0000 Subject: [PATCH 9/9] fmt --- .../tests/settings/voting-duration.spec.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/playwright-tests/tests/settings/voting-duration.spec.js b/playwright-tests/tests/settings/voting-duration.spec.js index 3c3f844b..89d87ca0 100644 --- a/playwright-tests/tests/settings/voting-duration.spec.js +++ b/playwright-tests/tests/settings/voting-duration.spec.js @@ -365,7 +365,9 @@ test.describe("admin connected", function () { ) ).toBeVisible(); await expect( - page.getByRole("heading", { name: "Impact of changing voting duration" }) + page.getByRole("heading", { + name: "Impact of changing voting duration", + }) ).toBeVisible(); await expect( await page.locator(".proposal-that-will-expire") @@ -382,16 +384,18 @@ test.describe("admin connected", function () { .locator(".modalfooter button", { hasText: "Cancel" }) .click(); await expect( - page.getByRole("heading", { name: "Impact of changing voting duration" }) + page.getByRole("heading", { + name: "Impact of changing voting duration", + }) ).not.toBeVisible(); } else if (expectedNewActiveProposals.length > 0) { await expect( - await page.getByText( - `${expectedNewActiveProposals.length} expired` - ) + await page.getByText(`${expectedNewActiveProposals.length} expired`) ).toBeVisible(); await expect( - page.getByRole("heading", { name: "Impact of changing voting duration" }) + page.getByRole("heading", { + name: "Impact of changing voting duration", + }) ).toBeVisible(); await expect( await page.locator(".proposal-that-will-be-active") @@ -406,7 +410,9 @@ test.describe("admin connected", function () { .locator(".modalfooter button", { hasText: "Cancel" }) .click(); await expect( - page.getByRole("heading", { name: "Impact of changing voting duration" }) + page.getByRole("heading", { + name: "Impact of changing voting duration", + }) ).not.toBeVisible(); } else { await expect(