Skip to content

Commit

Permalink
Merge pull request polkassembly#1539 from polkassembly/feature/kr/api…
Browse files Browse the repository at this point in the history
…-active-proposal-count

feat: ✨ active proposal count in sidebar
  • Loading branch information
alphainfinitus authored Apr 26, 2024
2 parents 026fe55 + 48c6f1a commit 8056fe0
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 20 deletions.
125 changes: 125 additions & 0 deletions pages/api/v1/posts/active-proposals-count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2019-2025 @polkassembly/polkassembly authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { NextApiHandler } from 'next';
import withErrorHandling from '~src/api-middlewares/withErrorHandling';
import { isValidNetwork } from '~src/api-utils';
import { MessageType } from '~src/auth/types';
import messages from '~src/auth/utils/messages';

import storeApiKeyUsage from '~src/api-middlewares/storeApiKeyUsage';
import apiErrorWithStatusCode from '~src/util/apiErrorWithStatusCode';
import { isOpenGovSupported } from '~src/global/openGovNetworks';
import fetchSubsquid from '~src/util/fetchSubsquid';
import {
GET_NETWORK_TRACK_ACTIVE_PROPOSALS_COUNT,
GOV1_NETWORK_ACTIVE_PROPOSALS_COUNT,
POLYMESH_NETWORK_ACTIVE_PROPOSALS_COUNT,
ZEITGEIST_NETWORK_ACTIVE_PROPOSALS_COUNT
} from '~src/queries';
import { IActiveProposalCount } from '~src/types';

export const getNetworkTrackActiveProposalsCount = async ({ network }: { network: string }) => {
try {
if (!network || !isValidNetwork(network)) throw apiErrorWithStatusCode(messages.INVALID_NETWORK, 400);

if (isOpenGovSupported(network)) {
const subsquidRes = await fetchSubsquid({
network,
query: GET_NETWORK_TRACK_ACTIVE_PROPOSALS_COUNT
});

const proposals = subsquidRes?.['data']?.proposals || [];
let proposalsCount: { [key: string]: number } = {};

proposals.map((proposal: { trackNumber: number }) => {
if (proposalsCount[proposal?.trackNumber] === undefined) {
proposalsCount[proposal?.trackNumber] = 1;
} else {
proposalsCount[proposal?.trackNumber] += 1;
}
});
proposalsCount = {
...proposalsCount,
allCount: subsquidRes?.['data']?.all?.totalCount || 0,
bountiesCount: subsquidRes?.['data']?.bountiesCount?.totalCount || 0,
childBountiesCount: subsquidRes?.['data']?.childBountiesCount?.totalCount || 0
};

return {
data: proposalsCount,
error: null,
status: 200
};
} else {
let query = GOV1_NETWORK_ACTIVE_PROPOSALS_COUNT;
if (network === 'zeitgeist') {
query = ZEITGEIST_NETWORK_ACTIVE_PROPOSALS_COUNT;
}
if (network === 'polymesh') {
query = POLYMESH_NETWORK_ACTIVE_PROPOSALS_COUNT;
}
const subsquidRes = await fetchSubsquid({
network,
query
});
const data = subsquidRes['data'];
let proposalsCount: IActiveProposalCount;
if (network === 'polymesh') {
proposalsCount = {
communityPipsCount: data?.communityPips?.totalCount || 0,
technicalPipsCount: data?.technicalPips.totalCount || 0,
upgradePipsCount: data?.upgradePips?.totalCount || 0
};
} else {
proposalsCount = {
councilMotionsCount: data?.councilMotions.totalCount || 0,
democracyProposalsCount: data?.democracyProposals.totalCount || 0,
referendumsCount: data?.referendums.totalCount || 0,
techCommetteeProposalsCount: data?.techCommitteeProposals.totalCount || 0,
tipsCount: data?.tips.totalCount || 0,
treasuryProposalsCount: data?.treasuryProposals.totalCount || 0
};
if (network === 'zeitgeist') {
proposalsCount = { ...proposalsCount, advisoryCommitteeMotionsCount: data?.advisoryCommitteeMotions.totalCount || 0 };
} else {
proposalsCount = {
...proposalsCount,
bountiesCount: data?.bounties?.totalCount || 0,
childBountiesCount: data?.childBounties.totalCount || 0
};
}
}
return {
data: proposalsCount,
error: null,
status: 200
};
}
} catch (error) {
return {
data: null,
error: error.message || messages.API_FETCH_ERROR,
status: Number(error.name) || 500
};
}
};

const handler: NextApiHandler<{ [key: string]: number } | MessageType> = async (req, res) => {
storeApiKeyUsage(req);

const network = String(req.headers['x-network']);

const { data, error, status } = await getNetworkTrackActiveProposalsCount({
network
});

if (error || !data) {
return res.status(status).json({ message: error || messages.API_FETCH_ERROR });
} else {
return res.status(status).json(data);
}
};

export default withErrorHandling(handler);
108 changes: 89 additions & 19 deletions src/components/AppLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { isFellowshipSupported } from '~src/global/fellowshipNetworks';
import { isGrantsSupported } from '~src/global/grantsNetworks';
import { isOpenGovSupported } from '~src/global/openGovNetworks';
import { networkTrackInfo } from '~src/global/post_trackInfo';
import { PostOrigin } from '~src/types';
import { IActiveProposalCount, PostOrigin } from '~src/types';

import Footer from './Footer';
import NavHeader from './NavHeader';
Expand All @@ -72,6 +72,7 @@ import BigToggleButton from '~src/ui-components/ToggleButton/BigToggleButton';
import TopNudges from '~src/ui-components/TopNudges';
import ImageIcon from '~src/ui-components/ImageIcon';
import { setOpenRemoveIdentityModal, setOpenRemoveIdentitySelectAddressModal } from '~src/redux/removeIdentity';
import nextApiClientFetch from '~src/util/nextApiClientFetch';

interface IUserDropdown {
handleSetIdentityClick: any;
Expand Down Expand Up @@ -308,8 +309,20 @@ const AppLayout = ({ className, Component, pageProps }: Props) => {
const [isGood, setIsGood] = useState<boolean>(false);
const [mainDisplay, setMainDisplay] = useState<string>('');
const dispatch = useDispatch();

// const [notificationVisible, setNotificationVisible] = useState(true);
const [totalActiveProposalsCount, setTotalActiveProposalsCount] = useState<IActiveProposalCount>();

const getTotalActiveProposalsCount = async () => {
if (!network) return;

const { data, error } = await nextApiClientFetch<IActiveProposalCount>('/api/v1/posts/active-proposals-count');
if (data) {
setTotalActiveProposalsCount(data);
} else if (error) {
console.log(error);
}
};

useEffect(() => {
const handleRouteChange = () => {
if (router.asPath.split('/')[1] !== 'discussions' && router.asPath.split('/')[1] !== 'post') {
Expand Down Expand Up @@ -341,6 +354,11 @@ const AppLayout = ({ className, Component, pageProps }: Props) => {
});
}, []);

useEffect(() => {
getTotalActiveProposalsCount();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [network]);

useEffect(() => {
if (!api || !apiReady) return;

Expand Down Expand Up @@ -413,24 +431,50 @@ const AppLayout = ({ className, Component, pageProps }: Props) => {
],
democracyItems: chainProperties[network]?.subsquidUrl
? [
getSiderMenuItem('Proposals', '/proposals', <DemocracyProposalsIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />),
getSiderMenuItem('Referenda', '/referenda', <ReferendaIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />)
getSiderMenuItem(
`Proposals ${totalActiveProposalsCount?.democracyProposalsCount ? totalActiveProposalsCount['democracyProposalsCount'] : ''}`,
'/proposals',
<DemocracyProposalsIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />
),
getSiderMenuItem(
`Referenda ${totalActiveProposalsCount?.referendumsCount ? totalActiveProposalsCount['referendumsCount'] : ''}`,
'/referenda',
<ReferendaIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />
)
]
: [],
councilItems: chainProperties[network]?.subsquidUrl
? [
getSiderMenuItem('Motions', '/motions', <MotionsIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />),
getSiderMenuItem(
`Motions ${totalActiveProposalsCount?.councilMotionsCount ? totalActiveProposalsCount['councilMotionsCount'] : ''}`,
'/motions',
<MotionsIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />
),
getSiderMenuItem('Members', '/council', <MembersIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />)
]
: [],
treasuryItems: chainProperties[network]?.subsquidUrl
? [
getSiderMenuItem('Proposals', '/treasury-proposals', <TreasuryProposalsIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />),
getSiderMenuItem('Tips', '/tips', <TipsIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />)
getSiderMenuItem(
`Proposals ${totalActiveProposalsCount?.treasuryProposalsCount ? totalActiveProposalsCount['treasuryProposalsCount'] : ''}`,
'/treasury-proposals',
<TreasuryProposalsIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />
),
getSiderMenuItem(
`Tips ${totalActiveProposalsCount?.tips ? totalActiveProposalsCount['tips'] : ''}`,
'/tips',
<TipsIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />
)
]
: [],
techCommItems: chainProperties[network]?.subsquidUrl
? [getSiderMenuItem('Proposals', '/tech-comm-proposals', <TechComProposalIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />)]
? [
getSiderMenuItem(
`Proposals ${totalActiveProposalsCount?.techCommetteeProposalsCount ? totalActiveProposalsCount['techCommetteeProposalsCount'] : ''}`,
'/tech-comm-proposals',
<TechComProposalIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />
)
]
: [],
allianceItems: chainProperties[network]?.subsquidUrl
? [
Expand All @@ -443,15 +487,31 @@ const AppLayout = ({ className, Component, pageProps }: Props) => {
PIPsItems:
chainProperties[network]?.subsquidUrl && network === AllNetworks.POLYMESH
? [
getSiderMenuItem('Technical Committee', '/technical', <RootIcon className='mt-1.5 scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />),
getSiderMenuItem('Upgrade Committee', '/upgrade', <UpgradeCommitteePIPsIcon className='mt-1.5 scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />),
getSiderMenuItem('Community', '/community', <CommunityPIPsIcon className='mt-1.5 scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />)
getSiderMenuItem(
`Technical Committee ${totalActiveProposalsCount?.technicalPipsCount ? totalActiveProposalsCount['technicalPipsCount'] : ''}`,
'/technical',
<RootIcon className='mt-1.5 scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />
),
getSiderMenuItem(
`Upgrade Committee ${totalActiveProposalsCount?.upgradePipsCount ? totalActiveProposalsCount['upgradePipsCount'] : ''}`,
'/upgrade',
<UpgradeCommitteePIPsIcon className='mt-1.5 scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />
),
getSiderMenuItem(
`Community ${totalActiveProposalsCount?.communityPipsCount ? totalActiveProposalsCount['communityPipsCount'] : ''}`,
'/community',
<CommunityPIPsIcon className='mt-1.5 scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />
)
]
: [],
AdvisoryCommittee:
chainProperties[network]?.subsquidUrl && network === AllNetworks.ZEITGEIST
? [
getSiderMenuItem('Motions', '/advisory-committee/motions', <MotionsIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />),
getSiderMenuItem(
`Motions ${totalActiveProposalsCount?.advisoryCommitteeMotionsCount ? totalActiveProposalsCount['advisoryCommitteeMotionsCount'] : ''}`,
'/advisory-committee/motions',
<MotionsIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />
),
getSiderMenuItem('Members', '/advisory-committee/members', <MembersIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />)
]
: []
Expand Down Expand Up @@ -572,13 +632,20 @@ const AppLayout = ({ className, Component, pageProps }: Props) => {
}

if (network && networkTrackInfo[network]) {
gov2TrackItems.mainItems.push(getSiderMenuItem('All', '/all-posts', <OverviewIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />));

gov2TrackItems.mainItems.push(
getSiderMenuItem(
`All ${totalActiveProposalsCount?.allCount ? totalActiveProposalsCount?.allCount : ''}`,
'/all-posts',
<OverviewIcon className='scale-90 font-medium text-lightBlue dark:text-icon-dark-inactive' />
)
);
for (const trackName of Object.keys(networkTrackInfo[network])) {
if (!networkTrackInfo[network][trackName] || !('group' in networkTrackInfo[network][trackName])) continue;

const activeProposal = totalActiveProposalsCount?.[networkTrackInfo[network][trackName]?.trackId];

const menuItem = getSiderMenuItem(
trackName.split(/(?=[A-Z])/).join(' '),
`${trackName.split(/(?=[A-Z])/).join(' ')} ${activeProposal ? activeProposal : ''}`,
`/${trackName
.split(/(?=[A-Z])/)
.join('-')
Expand All @@ -592,7 +659,7 @@ const AppLayout = ({ className, Component, pageProps }: Props) => {
case 'Treasury':
gov2TrackItems.treasuryItems.push(
getSiderMenuItem(
trackName.split(/(?=[A-Z])/).join(' '),
`${trackName.split(/(?=[A-Z])/).join(' ')} ${activeProposal ? activeProposal : ''}`,
`/${trackName
.split(/(?=[A-Z])/)
.join('-')
Expand All @@ -603,7 +670,7 @@ const AppLayout = ({ className, Component, pageProps }: Props) => {
case 'Whitelist':
gov2TrackItems.fellowshipItems.push(
getSiderMenuItem(
trackName.split(/(?=[A-Z])/).join(' '),
`${trackName.split(/(?=[A-Z])/).join(' ')} ${activeProposal ? activeProposal : ''}`,
`/${trackName
.split(/(?=[A-Z])/)
.join('-')
Expand All @@ -624,7 +691,7 @@ const AppLayout = ({ className, Component, pageProps }: Props) => {
);
gov2TrackItems.mainItems.push(
getSiderMenuItem(
trackName.split(/(?=[A-Z])/).join(' '),
`${trackName.split(/(?=[A-Z])/).join(' ')} ${activeProposal ? activeProposal : ''}`,
`/${trackName
.split(/(?=[A-Z])/)
.join('-')
Expand Down Expand Up @@ -723,7 +790,10 @@ const AppLayout = ({ className, Component, pageProps }: Props) => {
if (![AllNetworks.MOONBASE, AllNetworks.MOONBEAM, AllNetworks.MOONRIVER, AllNetworks.PICASSO].includes(network)) {
let items = [...gov2TrackItems.treasuryItems];
if (isOpenGovSupported(network)) {
items = items.concat(getSiderMenuItem('Bounties', '/bounties', null), getSiderMenuItem('Child Bounties', '/child_bounties', null));
items = items.concat(
getSiderMenuItem(`Bounties ${totalActiveProposalsCount?.['bountiesCount'] ? totalActiveProposalsCount?.['bountiesCount'] : ''}`, '/bounties', null),
getSiderMenuItem(`Child Bounties ${totalActiveProposalsCount?.['childBountiesCount'] ? totalActiveProposalsCount?.['childBountiesCount'] : ''}`, '/child_bounties', null)
);
}
gov2Items.splice(
-1,
Expand Down
3 changes: 2 additions & 1 deletion src/components/Post/GovernanceSideBar/PIPs/PIPsVote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import CustomButton from '~src/basic-components/buttons/CustomButton';
import ImageIcon from '~src/ui-components/ImageIcon';
import Alert from '~src/basic-components/Alert';
import SelectOption from '~src/basic-components/Select/SelectOption';
import classNames from 'classnames';

const ZERO_BN = new BN(0);

Expand Down Expand Up @@ -553,7 +554,7 @@ const PIPsVote = ({ className, referendumId, onAccountChange, lastVote, setLastV
if ([ProposalType.TECHNICAL_PIPS, ProposalType.UPGRADE_PIPS].includes(proposalType)) {
if (isPolymeshCommitteeMember) return VoteUI;

return <div className={className}>Only Polymesh Committee members may vote.</div>;
return <div className={classNames(className, 'dark:text-blue-dark-high')}>Only Polymesh Committee members may vote.</div>;
}

return VoteUI;
Expand Down
Loading

0 comments on commit 8056fe0

Please sign in to comment.