Skip to content

Commit

Permalink
settings page for voting duration (#121)
Browse files Browse the repository at this point in the history
https://github.com/user-attachments/assets/af591569-480e-4e9b-aa9e-16304380e33b

Lowering voting duration should warn and show proposals that will expire
because of the change


https://github.com/user-attachments/assets/d5829905-0643-4f57-96fe-b4abebe52c60

When a voting duration change proposal is submitted, a toast will show:


https://github.com/user-attachments/assets/1f004b39-d66c-4b1d-aa4a-57d5ed2047b2

The last test here uses a RPC sandbox, interacting with a real instance
of the smart contract. Not on the mainnet, but in a near-workspaces-rs
sandbox. This new sandbox facilitates real interaction with the smart
contracts in tests, without having to do real mainnet transactions. Also
there is a "playground" script which you can use to check how a contract
works. Currently only the sputnik-dao contract is deployed.



resolves #116
resolves #127
  • Loading branch information
petersalomonsen authored Nov 25, 2024
1 parent e7777fc commit 22161e3
Show file tree
Hide file tree
Showing 23 changed files with 5,135 additions and 13 deletions.
1 change: 1 addition & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"image": "mcr.microsoft.com/devcontainers/base:jammy",
"features": {
"ghcr.io/near/near-devcontainers/features/cargo-near:latest": {},
"ghcr.io/near/near-devcontainers/features/near-cli:latest": {},
Expand Down
3 changes: 3 additions & 0 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/bin/bash

sudo apt update
sudo apt install pkg-config

npm install
npx playwright install-deps
npx playwright install
27 changes: 27 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,30 @@ jobs:
run: |
${{ matrix.target_account.test_command }}
continue-on-error: false
playwright-tests-settings:
name: Playwright tests settings
runs-on: ubuntu-latest
strategy:
matrix:
target_account:
- test_command: npx playwright test --project=treasury-dashboard playwright-tests/tests/settings
- test_command: npx playwright test --project=infinex playwright-tests/tests/settings
- test_command: npx playwright test --project=treasury-testing playwright-tests/tests/settings
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: |
npm ci
npx playwright install-deps
npx playwright install
- name: Build project
run: npm run build
- name: Run tests
run: |
npm run build:sandbox
${{ matrix.target_account.test_command }}
continue-on-error: false
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ test-results
build
web4/treasury-web4
package-lock.json
sandboxrpc
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"rust-analyzer.linkedProjects": ["web4/treasury-web4/Cargo.toml"]
"rust-analyzer.linkedProjects": [
"web4/treasury-web4/Cargo.toml",
"sandboxrpc/Cargo.toml"
]
}
1 change: 1 addition & 0 deletions instances/treasury-devdao.near/widget/config/data.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ return {
showKYC: true,
showReferenceProposal: true,
showThresholdConfiguration: false,
showVotingDurationConfiguration: false,
logo: (
<svg
width="48"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
const instance = props.instance;
if (!instance) {
return <></>;
}

const { treasuryDaoID } = VM.require(`${instance}/widget/config.data`);

const daoPolicy = Near.view(treasuryDaoID, "get_policy", {});

if (!daoPolicy) {
return <></>;
}

const lastProposalId = Near.view(treasuryDaoID, "get_last_proposal_id");

const deposit = daoPolicy?.proposal_bond || 100000000000000000000000;

const currentDurationDays =
Number(
daoPolicy.proposal_period.substr(
0,
daoPolicy.proposal_period.length - "000000000".length
)
) /
(60 * 60 * 24);

const [durationDays, setDurationDays] = useState(currentDurationDays);
const [proposalsThatWillExpire, setProposalsThatWillExpire] = useState([]);
const [showToastStatus, setToastStatus] = useState(null);
const [isSubmittingChangeRequest, setSubmittingChangeRequest] = useState(false);

const Container = styled.div`
font-size: 14px;
.border-right {
border-right: 1px solid rgba(226, 230, 236, 1);
}
.card-title {
font-size: 18px;
font-weight: 600;
padding-block: 5px;
border-bottom: 1px solid rgba(226, 230, 236, 1);
}
.selected-role {
background-color: rgba(244, 244, 244, 1);
}
.cursor-pointer {
cursor: pointer;
}
.tag {
background-color: rgba(244, 244, 244, 1);
font-size: 12px;
padding-block: 5px;
}
label {
color: rgba(153, 153, 153, 1);
font-size: 12px;
}
.fw-bold {
font-weight: 500 !important;
}
.p-0 {
padding: 0 !important;
}
.text-md {
font-size: 13px;
}
.theme-btn {
background-color: var(--theme-color) !important;
color: white;
}
.warning {
background-color: rgba(255, 158, 0, 0.1);
color: rgba(177, 113, 8, 1);
font-weight: 500;
}
.text-sm {
font-size: 12px !important;
}
.text-muted {
color: rgba(153, 153, 153, 1);
}
.text-red {
color: #d95c4a;
}
.toast {
background: white !important;
}
.toast-header {
background-color: #2c3e50 !important;
color: white !important;
}
`;

const ToastContainer = styled.div`
a {
color: black !important;
text-decoration: underline !important;
&:hover {
color: black !important;
}
}
`;

const cancelChangeRequest = () => {
setDurationDays(currentDurationDays);
};

const submitChangeRequest = () => {
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 limit = 10;
if (newDurationDays < currentDurationDays) {
const fetchProposalsThatWillExpire = (
lastIndex,
newProposalsThatWillExpire
) => {
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 * newDurationDays;
if (
currentExpiryTime >= now &&
newExpiryTime < now &&
proposal.status === "InProgress"
) {
newProposalsThatWillExpire.push({
currentExpiryTime,
newExpiryTime,
submissionTimeMillis,
...proposal,
});
}
fetchMore = currentExpiryTime >= now;
}
setProposalsThatWillExpire(newProposalsThatWillExpire);
if (fetchMore) {
fetchProposalsThatWillExpire(
lastIndex - limit,
newProposalsThatWillExpire
);
}
});
};
fetchProposalsThatWillExpire(lastProposalId, []);
}
};

return (
<Container>
<div className="card rounded-3" style={{ maxWidth: "50rem" }}>
<div className="card-title px-3">Voting Duration</div>
<div className="card-body">
<p>
Set the number of days a vote is active. A decision expires if voting
is not completed within this period.
</p>
<p>
<label for="exampleInputEmail1" class="px-3">
Number of days
</label>
<input
type="number"
class="form-control"
aria-describedby="votingDurationHelp"
placeholder="Enter voting duration days"
value={durationDays}
onChange={(event) => changeDurationDays(event.target.value)}
></input>
<small id="votingDurationHelp" class="form-text text-muted px-3">
Enter number of days that a vote should be active
</small>
</p>

{proposalsThatWillExpire.length > 0 ? (
<p>
<div class="alert alert-danger" role="alert">
The following proposals will expire because of the changed
duration
</div>
<table className="table table-sm">
<thead>
<tr className="text-grey">
<th>Id</th>
<th>Description</th>
<th>Submission date</th>
<th>Current expiry</th>
<th>New expiry</th>
</tr>
</thead>
<tbody>
{proposalsThatWillExpire.map((proposal) => (
<tr class="proposal-that-will-expire">
<td>{proposal.id}</td>
<td>{proposal.description}</td>
<td>
{new Date(proposal.submissionTimeMillis)
.toJSON()
.substring(0, "yyyy-mm-dd".length)}
</td>
<td>
{new Date(proposal.currentExpiryTime)
.toJSON()
.substring(0, "yyyy-mm-dd".length)}
</td>
<td>
{new Date(proposal.newExpiryTime)
.toJSON()
.substring(0, "yyyy-mm-dd".length)}
</td>
</tr>
))}
</tbody>
</table>
</p>
) : (
""
)}

<button class="btn btn-light" onClick={cancelChangeRequest}>
Cancel
</button>
<button class="btn btn-success" onClick={submitChangeRequest}>
Submit Request
</button>
</div>
</div>
<ToastContainer className="toast-container position-fixed bottom-0 end-0 p-3">
<div className={`toast ${showToastStatus ? "show" : ""}`}>
<div className="toast-header px-2">
<strong className="me-auto">Just Now</strong>
<i className="bi bi-x-lg h6" onClick={() => setToastStatus(null)}></i>
</div>
<div className="toast-body">
<p>Voting duration change request submitted</p>
</div>
</div>
</ToastContainer>
</Container>
);
Loading

0 comments on commit 22161e3

Please sign in to comment.