From 4303e2bb5b851707cc8c8c7e752d7bcb4afa40b6 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Tue, 14 May 2019 10:14:36 +0800 Subject: [PATCH 1/2] modify some graphql types to Enum (#39) * modify some graphql types to Enum * remove console log * utility function to get actionableStatus * query for fetching actionable statuses * fetchProposals query with actionableStatus field * add default prl for special proposal * add arg for filtering out non-actionable proposals * refactor * minor fix * case fix * graphql case fix * fix for 0 proposal count * refactoring --- graphql.js | 27 +++++++++- helpers/constants.js | 39 ++++++++++----- helpers/contracts.js | 3 +- helpers/utils.js | 84 +++++++++++++++++++++++++++++++ routes/proposals.js | 6 +-- scripts/proposals.js | 5 +- types/proposal.js | 116 ++++++++++++++++++++++++++++++++++++++++--- 7 files changed, 253 insertions(+), 27 deletions(-) diff --git a/graphql.js b/graphql.js index f9db6f3..5078578 100644 --- a/graphql.js +++ b/graphql.js @@ -2,14 +2,22 @@ const ethJsUtil = require('ethereumjs-util'); const { withFilter, AuthenticationError } = require('apollo-server'); const { ApolloServer, gql } = require('apollo-server-express'); -const { proposalToType } = require('./helpers/utils'); +const { actionableStatus } = require('./helpers/constants'); +const { proposalToType, getCurrentActionableStatus } = require('./helpers/utils'); const { getAddressDetails } = require('./dbWrapper/addresses'); const { getDaoInfo } = require('./dbWrapper/dao'); const { pubsub } = require('./pubsub'); -const { getProposal, getSpecialProposal } = require('./dbWrapper/proposals'); +const { + getProposal, + getSpecialProposal, + getSpecialProposals, + getProposals, +} = require('./dbWrapper/proposals'); + +const { proposalStages } = require('./helpers/constants'); const { typeDef: scalarType, resolvers: scalarResolvers } = require('./types/scalar.js'); const { typeDef: userType, resolvers: userResolvers } = require('./types/user.js'); @@ -26,6 +34,9 @@ const queryType = gql` # Get the current user's information. fetchDao: Dao! + + # Find proposals in specific stage + fetchProposals(stage: String!, onlyActionable: Boolean): [Proposal] } `; @@ -77,6 +88,18 @@ const resolvers = { fetchDao: (_obj, _args, _context, _info) => { return getDaoInfo(); }, + fetchProposals: async (_obj, args, context, _info) => { + const { stage, onlyActionable } = args; + const filter = (stage === 'all') ? {} : { stage: stage.toUpperCase() }; + const proposals = await getProposals(filter); + const specialProposals = (stage.toUpperCase() === proposalStages.PROPOSAL || stage === 'all') ? await getSpecialProposals() : []; + + const allProposals = specialProposals.concat(proposals).map(proposal => ({ + ...proposal, + actionableStatus: getCurrentActionableStatus(proposal, context.currentUser), + })); + return onlyActionable ? allProposals.filter(proposal => proposal.actionableStatus !== actionableStatus.NONE) : allProposals; + }, }, Mutation: {}, Subscription: { diff --git a/helpers/constants.js b/helpers/constants.js index 0d6c66c..ff4ea01 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -115,25 +115,37 @@ const readProposalVersionIndices = { }; const readProposalPRLActions = { - 1: true, - 2: true, - 3: false, + 1: 'STOPPED', + 2: 'PAUSED', + 3: 'ACTIVE', + NEW: 'ACTIVE', + PAUSED: 'PAUSED', }; const proposalStages = { - IDEA: 'idea', - DRAFT: 'draft', - PROPOSAL: 'proposal', - ONGOING: 'ongoing', - REVIEW: 'review', - ARCHIVED: 'archived', + IDEA: 'IDEA', + DRAFT: 'DRAFT', + PROPOSAL: 'PROPOSAL', + ONGOING: 'ONGOING', + REVIEW: 'REVIEW', + ARCHIVED: 'ARCHIVED', }; const proposalVotingStages = { - DRAFT: 'draftVoting', - COMMIT: 'commit', - REVEAL: 'reveal', - NONE: 'none', + DRAFT: 'DRAFT', + COMMIT: 'COMMIT', + REVEAL: 'REVEAL', + NONE: 'NONE', +}; + +const actionableStatus = { + NONE: 'NONE', + AWAITING_ENDORSEMENT: 'AWAITING_ENDORSEMENT', + MODERATOR_VOTING: 'MODERATOR_VOTING_ACTIVE', + COMMIT_PHASE: 'COMMIT_PHASE_ACTIVE', + REVEAL_PHASE: 'REVEAL_PHASE_ACTIVE', + CLAIM_FUNDING: 'CLAIM_FUNDING', + CLAIM_VOTING: 'CLAIM_VOTING', }; const collections = { @@ -342,4 +354,5 @@ module.exports = { daoServerEndpoints, daoServerEventTypes, gasLimits, + actionableStatus, }; diff --git a/helpers/contracts.js b/helpers/contracts.js index dc76b00..1c0d05a 100644 --- a/helpers/contracts.js +++ b/helpers/contracts.js @@ -1,7 +1,6 @@ const ContractResolver = require('@digix/dao-contracts/build/contracts/ContractResolver.json'); const Dao = require('@digix/dao-contracts/build/contracts/Dao.json'); const DaoCalculatorService = require('@digix/dao-contracts/build/contracts/DaoCalculatorService.json'); -const DaoConfigsStorage = require('@digix/dao-contracts/build/contracts/DaoConfigsStorage.json'); const DaoFundingManager = require('@digix/dao-contracts/build/contracts/DaoFundingManager.json'); const DaoIdentity = require('@digix/dao-contracts/build/contracts/DaoIdentity.json'); const DaoIdentityStorage = require('@digix/dao-contracts/build/contracts/DaoIdentityStorage.json'); @@ -17,7 +16,9 @@ const DaoStakeStorage = require('@digix/dao-contracts/build/contracts/DaoStakeSt const DaoStorage = require('@digix/dao-contracts/build/contracts/DaoStorage.json'); // Replace with DaoUpgradeStorage in mainnet deployment +// Replace with DaoConfigsStorage in mainnet deployment const DaoUpgradeStorage = require('@digix/dao-contracts/build/contracts/MockDaoUpgradeStorage.json'); +const DaoConfigsStorage = require('@digix/dao-contracts/build/contracts/MockDaoConfigsStorage.json'); const DaoVoting = require('@digix/dao-contracts/build/contracts/DaoVoting.json'); const DaoVotingClaims = require('@digix/dao-contracts/build/contracts/DaoVotingClaims.json'); diff --git a/helpers/utils.js b/helpers/utils.js index 92a715e..3256cb4 100644 --- a/helpers/utils.js +++ b/helpers/utils.js @@ -1,10 +1,17 @@ const BigNumber = require('bignumber.js'); const crypto = require('crypto'); +const { + getCurrentTimestamp, +} = require('@digix/helpers/lib/helpers'); + const { denominators, dijixDefaultFields, gasLimits, + actionableStatus, + proposalStages, + proposalVotingStages, } = require('./constants'); const getServerSignatures = function (req) { @@ -359,6 +366,82 @@ const getTxConfigs = function () { }; }; +const getCurrentActionableStatus = function (proposal, user) { + if (!proposal || !user) return actionableStatus.NONE; + + const currentTime = getCurrentTimestamp(); + if ( + proposal.stage === proposalStages.IDEA + && user.isModerator + ) { + return actionableStatus.AWAITING_ENDORSEMENT; + } + if ( + proposal.votingStage === proposalVotingStages.DRAFT + && currentTime > proposal.draftVoting.startTime + && currentTime < proposal.draftVoting.votingDeadline + && user.isModerator + ) { + return actionableStatus.MODERATOR_VOTING; + } + if ( + proposal.votingStage === proposalVotingStages.DRAFT + && currentTime > proposal.draftVoting.votingDeadline + && user.address === proposal.proposer + ) { + return actionableStatus.CLAIM_VOTING; + } + if ( + proposal.votingStage === proposalVotingStages.COMMIT + && currentTime > proposal.votingRounds[proposal.currentVotingRound].startTime + && currentTime < proposal.votingRounds[proposal.currentVotingRound].commitDeadline + && (user.isModerator || user.isParticipant) + ) { + return actionableStatus.COMMIT_PHASE; + } + if ( + proposal.votingStage === proposalVotingStages.COMMIT + && currentTime > proposal.votingRounds[proposal.currentVotingRound].commitDeadline + && currentTime < proposal.votingRounds[proposal.currentVotingRound].revealDeadline + && (user.isModerator || user.isParticipant) + ) { + return actionableStatus.REVEAL_PHASE; + } + if ( + proposal.votingStage === proposalVotingStages.COMMIT + && currentTime > proposal.votingRounds[proposal.currentVotingRound].revealDeadline + && user.address === proposal.proposer + ) { + return actionableStatus.CLAIM_VOTING; + } + if ( + proposal.stage === proposalStages.ONGOING + && proposal.claimableFunding !== null + && proposal.claimableFunding !== undefined + && proposal.claimableFunding !== '0' + && user.address === proposal.proposer + ) { + return actionableStatus.CLAIM_FUNDING; + } + if ( + proposal.isActive + && currentTime > proposal.votingRounds[0].startTime + && currentTime < proposal.votingRounds[0].commitDeadline + && (user.isModerator || user.isParticipant) + ) { + return actionableStatus.COMMIT_PHASE; + } + if ( + proposal.isActive + && currentTime > proposal.votingRounds[0].commitDeadline + && currentTime < proposal.votingRounds[0].revealDeadline + && (user.isModerator || user.isParticipant) + ) { + return actionableStatus.REVEAL_PHASE; + } + return actionableStatus.NONE; +}; + module.exports = { sumArray, sumArrayBN, @@ -386,4 +469,5 @@ module.exports = { getDefaultDijixFields, getAdditionalDocs, getTxConfigs, + getCurrentActionableStatus, }; diff --git a/routes/proposals.js b/routes/proposals.js index 8ea77ef..debbfd0 100644 --- a/routes/proposals.js +++ b/routes/proposals.js @@ -34,7 +34,7 @@ router.get('/count', async (req, res) => { if (!result[proposal.stage]) result[proposal.stage] = 0; result[proposal.stage] += 1; } - result[proposalStages.PROPOSAL] += specialProposalsCount; + result[proposalStages.PROPOSAL] = result[proposalStages.PROPOSAL] ? result[proposalStages.PROPOSAL] + specialProposalsCount : specialProposalsCount; return res.json({ result }); }); @@ -45,11 +45,11 @@ router.get('/details/:id', async (req, res) => { }); router.get('/:stage', async (req, res) => { - const filter = (req.params.stage === 'all') ? {} : { stage: req.params.stage }; + const filter = (req.params.stage === 'all') ? {} : { stage: req.params.stage.toUpperCase() }; const proposals = await getProposals(filter); let specialProposals = []; if ( - req.params.stage === proposalStages.PROPOSAL + req.params.stage.toUpperCase() === proposalStages.PROPOSAL || req.params.stage === 'all' ) { specialProposals = await getSpecialProposals(); diff --git a/scripts/proposals.js b/scripts/proposals.js index 41f8614..11ab3c7 100644 --- a/scripts/proposals.js +++ b/scripts/proposals.js @@ -89,7 +89,7 @@ const refreshProposalNew = async (res) => { proposal.stage = proposalStages.IDEA; proposal.timeCreated = proposalDetails[readProposalIndices.timeCreated].toNumber(); proposal.finalVersionIpfsDoc = proposalDetails[readProposalIndices.finalVersionIpfsDoc]; - proposal.prl = proposalDetails[readProposalIndices.prl]; + proposal.prl = readProposalPRLActions.NEW; proposal.isDigix = proposalDetails[readProposalIndices.isDigix]; proposal.claimableFunding = 0; proposal.currentMilestone = -1; @@ -159,7 +159,7 @@ const refreshProposalDetails = async (res) => { proposal.endorser = proposalDetails[readProposalIndices.endorser]; proposal.timeCreated = proposalDetails[readProposalIndices.timeCreated].toNumber(); proposal.finalVersionIpfsDoc = proposalDetails[readProposalIndices.finalVersionIpfsDoc]; - proposal.prl = proposalDetails[readProposalIndices.prl]; + proposal.prl = proposalDetails[readProposalIndices.prl] ? readProposalPRLActions.PAUSED : readProposalPRLActions.NEW; proposal.isDigix = proposalDetails[readProposalIndices.isDigix]; const nVersions = proposalDetails[readProposalIndices.nVersions]; @@ -733,6 +733,7 @@ const refreshProposalSpecialNew = async (res) => { proposal.timeCreated = readProposal[readSpecialProposalIndices.timeCreated].toNumber(); proposal.isActive = false; proposal.isSpecial = true; + proposal.prl = readProposalPRLActions.NEW; proposal.stage = proposalStages.PROPOSAL; proposal.uintConfigs = {}; proposal.addressConfigs = {}; diff --git a/types/proposal.js b/types/proposal.js index de4dd8f..cd810f3 100644 --- a/types/proposal.js +++ b/types/proposal.js @@ -1,9 +1,105 @@ const { gql } = require('apollo-server-express'); -const { ofOne } = require('../helpers/utils'); -const { denominators } = require('../helpers/constants'); +const { + ofOne, + getCurrentActionableStatus, +} = require('../helpers/utils'); + +const { + denominators, + actionableStatus, +} = require('../helpers/constants'); const typeDef = gql` + enum ProposalPrlEnum { + # If PRL stops proposal + STOPPED + + # If PRL pauses proposal + PAUSED + + # If no action has been taken + # the proposal is active + ACTIVE + } + + enum ProposalStageEnum { + # Idea phase + IDEA + + # Draft phase + DRAFT + + # Proposal phase + PROPOSAL + + # Ongoing phase + ONGOING + + # Review phase + REVIEW + + # Archived phase + # After a proposal is completed all rounds + # Or if it failed voting in any round + # Or the proposer closed the proposal + # Or the founder closed the proposal after deadline + ARCHIVED + } + + enum ProposalVotingStageEnum { + # Draft Voting stage + DRAFT + + # Commit voting stage + COMMIT + + # Reveal voting stage + REVEAL + + # No voting stage going on + NONE + } + + enum ActionableStatusEnum { + # No actionable status + # Or there is no user context + NONE + + # Proposal awaits endorsement + # is displayed to the moderators + AWAITING_ENDORSEMENT + + # Proposal is in moderator voting phase + # is displayed to the moderators + MODERATOR_VOTING + + # Proposal is in commit voting phase + # is displayed to every participant + COMMIT_PHASE + + # Proposal is in reveal vote phase + # is displayed to every participant + REVEAL_PHASE + + # Funds can be claimed for this proposal + # is displayed only to the proposer of this proposal + CLAIM_FUNDING + + # Voting result can be claimed for this proposal + # is displayed only to the proposer of this proposal + CLAIM_VOTING + } + + # Proposal actionable status for a proposal + type ProposalActionableObject { + # Proposal ID + proposalId: String + + # Action that can be done by the current user + actionableStatus: String + } + # Voting rounds for proposal voting type Milestone { # Index ID @@ -233,7 +329,7 @@ const typeDef = gql` endorser: EthAddress # The current stage of the proposal - stage: String + stage: ProposalStageEnum # A flag to indicate the proposal is by the Digix isDigix: Boolean @@ -281,13 +377,13 @@ const typeDef = gql` finalVersionIpfsDoc: String # See 'Proposal.isPrl' - prl: Boolean + prl: ProposalPrlEnum # Proposal's claimable funding claimableFunding: BigNumber # Current voting stage - votingStage: String + votingStage: ProposalVotingStageEnum # For special proposals, the title of the proposal title: String @@ -300,6 +396,9 @@ const typeDef = gql` # Special proposal config changes uintConfigs: UintConfig + + # Any actionable status + actionableStatus: ActionableStatusEnum } `; @@ -354,6 +453,12 @@ const resolvers = { claimableFunding(proposal) { return eth(proposal.claimableFunding); }, + actionableStatus(proposal, _args, context, _info) { + if (!context.currentUser) { + return actionableStatus.NONE; + } + return (proposal.actionableStatus || getCurrentActionableStatus(proposal, context.currentUser)); + }, proposalVersions(proposal) { return (proposal.proposalVersions || []).map((version, index) => ({ id: `${proposal.proposalId}/VERSION-${index}`, @@ -361,7 +466,6 @@ const resolvers = { })); }, }, - }; module.exports = { resolvers, typeDef }; From 659e4d73984dd5af921abcf56df60c59f8054c61 Mon Sep 17 00:00:00 2001 From: Francis Murillo <44824731+FrancisMurilloDigix@users.noreply.github.com> Date: Tue, 14 May 2019 14:31:00 +0800 Subject: [PATCH 2/2] [fix] Fix actionableStatus enum suffixes (#41) --- helpers/constants.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helpers/constants.js b/helpers/constants.js index ff4ea01..df7df46 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -141,9 +141,9 @@ const proposalVotingStages = { const actionableStatus = { NONE: 'NONE', AWAITING_ENDORSEMENT: 'AWAITING_ENDORSEMENT', - MODERATOR_VOTING: 'MODERATOR_VOTING_ACTIVE', - COMMIT_PHASE: 'COMMIT_PHASE_ACTIVE', - REVEAL_PHASE: 'REVEAL_PHASE_ACTIVE', + MODERATOR_VOTING: 'MODERATOR_VOTING', + COMMIT_PHASE: 'COMMIT_PHASE', + REVEAL_PHASE: 'REVEAL_PHASE', CLAIM_FUNDING: 'CLAIM_FUNDING', CLAIM_VOTING: 'CLAIM_VOTING', };