Skip to content

Commit

Permalink
feat: fetch proposals
Browse files Browse the repository at this point in the history
  • Loading branch information
shuffledex committed Jul 23, 2020
1 parent d366b1e commit 496b144
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 15 deletions.
59 changes: 57 additions & 2 deletions src/Governance.ts
@@ -1,8 +1,14 @@
import { ApolloQueryResult } from 'apollo-client';
import { BigNumber } from 'bignumber.js';

import { Identity, Proposal } from '~/api/entities';
import { createProposal, CreateProposalParams } from '~/api/procedures';
import { TransactionQueue } from '~/base';
import { PolymeshError, TransactionQueue } from '~/base';
import { Context } from '~/context';
import { identityIdToString } from '~/utils';
import { proposals } from '~/middleware/queries';
import { ProposalOrderByInput, ProposalState, Query } from '~/middleware/types';
import { Ensured, ErrorCode } from '~/types';
import { identityIdToString, valueToDid } from '~/utils';

/**
* Handles all Governance related functionality
Expand Down Expand Up @@ -35,6 +41,55 @@ export class Governance {
return activeMembers.map(member => new Identity({ did: identityIdToString(member) }, context));
}

/**
* Retrieve a list of proposals. Can be filtered using parameters
*
* @param opts.proposers - identities (or identity IDs) for which to fetch proposals (proposers). Defaults to all proposers
* @param opts.states - state of the proposal. Defaults to all states
* @param opts.orderBy - the order in witch the proposals are returned. Defaults to created at in ascendent order
* @param opts.size - page size
* @param opts.start - page offset
*/
public async getProposals(
opts: {
proposers?: (string | Identity)[];
states?: ProposalState[];
orderBy?: ProposalOrderByInput;
size?: number;
start?: number;
} = {}
): Promise<Proposal[]> {
const {
context: { middlewareApi },
context,
} = this;

const { proposers, states, orderBy, size, start } = opts;

let result: ApolloQueryResult<Ensured<Query, 'proposals'>>;

try {
result = await middlewareApi.query<Ensured<Query, 'proposals'>>(
proposals({
proposers: proposers?.map(proposer => valueToDid(proposer)),
states,
orderBy,
count: size,
skip: start,
})
);
} catch (e) {
throw new PolymeshError({
code: ErrorCode.MiddlewareError,
message: `Error in middleware query: ${e.message}`,
});
}

return result.data.proposals.map(
({ pipId }) => new Proposal({ pipId: new BigNumber(pipId) }, context)
);
}

/**
* Create a proposal
*
Expand Down
74 changes: 64 additions & 10 deletions src/__tests__/Governance.ts
Expand Up @@ -4,22 +4,15 @@ import sinon from 'sinon';
import { Identity, Proposal } from '~/api/entities';
import { createProposal } from '~/api/procedures';
import { TransactionQueue } from '~/base';
import { Context } from '~/context';
import { Governance } from '~/Governance';
import { proposals } from '~/middleware/queries';
import { Proposal as MiddlewareProposal, ProposalState } from '~/middleware/types';
import { dsMockUtils } from '~/testUtils/mocks';
import { TxTags } from '~/types';

describe('Governance class', () => {
let context: Context;
let governance: Governance;

beforeAll(() => {
dsMockUtils.initMocks();
context = dsMockUtils.getContextInstance();
});

beforeEach(() => {
governance = new Governance(context);
});

afterEach(() => {
Expand All @@ -37,7 +30,9 @@ describe('Governance class', () => {

test('should retrieve a list of the identities of all active members', async () => {
const did = 'someDid';
const context = dsMockUtils.getContextInstance();
const expectedMembers = [new Identity({ did }, context)];
const governance = new Governance(context);

dsMockUtils.createQueryStub('committeeMembership', 'activeMembers', {
returnValue: [dsMockUtils.createMockIdentityId('someDid')],
Expand All @@ -49,6 +44,64 @@ describe('Governance class', () => {
});
});

describe('method: getProposals', () => {
test('should return a list of proposal entities', async () => {
const pipId = 10;
const proposerDid = 'someProposerDid';
const createdAt = 50800;
const coolOffPeriod = 100;
const proposalPeriodTimeFrame = 600;
const context = dsMockUtils.getContextInstance();
const fakeResult = [new Proposal({ pipId: new BigNumber(pipId) }, context)];
const proposalsQueryResponse: MiddlewareProposal[] = [
{
pipId: pipId,
proposer: proposerDid,
createdAt,
url: 'http://someUrl',
description: 'some description',
coolOffEndBlock: createdAt + coolOffPeriod,
endBlock: createdAt + proposalPeriodTimeFrame,
proposal: '0x180500cc829c190000000000000000000000e8030000',
lastState: ProposalState.Referendum,
lastStateUpdatedAt: createdAt + proposalPeriodTimeFrame,
totalVotes: 0,
totalAyesWeight: 0,
totalNaysWeight: 0,
},
];
const governance = new Governance(context);

dsMockUtils.createApolloQueryStub(
proposals({
proposers: [proposerDid],
states: undefined,
orderBy: undefined,
count: undefined,
skip: undefined,
}),
{
proposals: proposalsQueryResponse,
}
);

const result = await governance.getProposals({
proposers: [proposerDid],
});

expect(result).toEqual(fakeResult);
});

test('should throw if the middleware query fails', async () => {
const context = dsMockUtils.getContextInstance();
const governance = new Governance(context);

dsMockUtils.throwOnMiddlewareQuery();

return expect(governance.getProposals()).rejects.toThrow('Error in middleware query: Error');
});
});

describe('method: createProposal', () => {
test('should prepare the procedure with the correct arguments and context, and return the resulting transaction queue', async () => {
const args = {
Expand All @@ -58,7 +111,8 @@ describe('Governance class', () => {
tag: TxTags.asset.RegisterTicker,
args: ['someTicker'],
};

const context = dsMockUtils.getContextInstance();
const governance = new Governance(context);
const expectedQueue = ('someQueue' as unknown) as TransactionQueue<Proposal>;

sinon
Expand Down
29 changes: 27 additions & 2 deletions src/middleware/__tests__/queries.ts
@@ -1,6 +1,13 @@
import { ClaimTypeEnum, EventIdEnum,ModuleIdEnum } from '~/middleware/types';
import {
ClaimTypeEnum,
EventIdEnum,
ModuleIdEnum,
Order,
ProposalOrderFields,
ProposalState,
} from '~/middleware/types';

import { didsWithClaims, eventByIndexedArgs } from '../queries';
import { didsWithClaims, eventByIndexedArgs, proposals } from '../queries';

describe('didsWithClaims', () => {
test('should pass the variables to the grapqhl query', () => {
Expand Down Expand Up @@ -34,3 +41,21 @@ describe('eventByIndexedArgs', () => {
expect(result.variables).toEqual(variables);
});
});

describe('proposals', () => {
test('should pass the variables to the grapqhl query', () => {
const variables = {
proposers: ['someProposer'],
states: [ProposalState.Referendum],
orderBy: {
field: ProposalOrderFields.CreatedAt,
order: Order.Desc,
},
};

const result = proposals(variables);

expect(result.query).toBeDefined();
expect(result.variables).toEqual(variables);
});
});
54 changes: 53 additions & 1 deletion src/middleware/queries.ts
@@ -1,6 +1,10 @@
import gql from 'graphql-tag';

import { QueryDidsWithClaimsArgs, QueryEventsByIndexedArgsArgs } from '~/middleware/types';
import {
QueryDidsWithClaimsArgs,
QueryEventsByIndexedArgsArgs,
QueryProposalsArgs,
} from '~/middleware/types';
import { GraphqlQuery } from '~/types/internal';

/**
Expand Down Expand Up @@ -87,3 +91,51 @@ export function eventByIndexedArgs(
variables,
};
}

/**
* @hidden
*
* Get all proposals optionally filtered by pipId, proposer or state
*/
export function proposals(
variables?: QueryProposalsArgs
): GraphqlQuery<QueryProposalsArgs | undefined> {
const query = gql`
query ProposalsQuery(
$pipIds: [Int!]
$proposers: [String!]
$states: [ProposalState!]
$count: Int
$skip: Int
$orderBy: ProposalOrderByInput
) {
proposals(
pipIds: $pipIds
proposers: $proposers
states: $states
count: $count
skip: $skip
orderBy: $orderBy
) {
pipId
proposer
createdAt
url
description
coolOffEndBlock
endBlock
proposal
lastState
lastStateUpdatedAt
totalVotes
totalAyesWeight
totalNaysWeight
}
}
`;

return {
query,
variables,
};
}

0 comments on commit 496b144

Please sign in to comment.