Skip to content

Commit

Permalink
all track data api
Browse files Browse the repository at this point in the history
  • Loading branch information
KanishkaRajputd committed Apr 25, 2024
1 parent f3f8f93 commit d5c196a
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 4 deletions.
58 changes: 58 additions & 0 deletions pages/api/v1/trackLevelAnalytics/all-track-analytics-stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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 type { 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 fetchSubsquid from '~src/util/fetchSubsquid';
import { GET_ALL_TRACK_LEVEL_ANALYTICS_STATS } from '~src/queries';
import dayjs from 'dayjs';
import apiErrorWithStatusCode from '~src/util/apiErrorWithStatusCode';
import { ITrackAnalyticsStats } from '~src/redux/trackLevelAnalytics/@types';

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

try {
const subsquidRes = await fetchSubsquid({
network,
query: GET_ALL_TRACK_LEVEL_ANALYTICS_STATS,
variables: {
before: dayjs().subtract(7, 'days').toISOString()
}
});
const data = subsquidRes['data'];
const diffActiveProposals = (data?.diffActiveProposals?.totalCount * 100) / data?.totalActiveProposals?.totalCount;
const diffProposalCount = (data?.diffProposalCount?.totalCount * 100) / data?.totalProposalCount.totalCount;
return {
data: {
activeProposals: { diff: diffActiveProposals.toFixed(2) || 0, total: data?.totalActiveProposals?.totalCount || 0 },
allProposals: { diff: diffProposalCount.toFixed(2) || 0, total: data?.totalProposalCount?.totalCount || 0 }
},
error: null
};
} catch (err) {
return { data: null, error: err || messages.API_FETCH_ERROR };
}
};

const handler: NextApiHandler<ITrackAnalyticsStats | MessageType> = async (req, res) => {
storeApiKeyUsage(req);

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

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

if (data) {
return res.status(200).json(data as ITrackAnalyticsStats);
} else {
return res.status(500).json({ message: error || 'Activities count not found!' });
}
};
export default withErrorHandling(handler);
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// 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 type { NextApiRequest, NextApiResponse } from 'next';
import withErrorHandling from '~src/api-middlewares/withErrorHandling';
import { isValidNetwork } from '~src/api-utils';
import { MessageType } from '~src/auth/types';
import fetchSubsquid from '~src/util/fetchSubsquid';
import { GET_ALL_TRACK_LEVEL_ANALYTICS_DELEGATION_DATA } from '~src/queries';
import BN from 'bn.js';
import storeApiKeyUsage from '~src/api-middlewares/storeApiKeyUsage';
import messages from '~src/auth/utils/messages';
import apiErrorWithStatusCode from '~src/util/apiErrorWithStatusCode';
import { IDelegationAnalytics, IDelegatorsAndDelegatees } from '~src/redux/trackLevelAnalytics/@types';

const ZERO_BN = new BN(0);

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

const data = await fetchSubsquid({
network,
query: GET_ALL_TRACK_LEVEL_ANALYTICS_DELEGATION_DATA
});

let totalCapital = ZERO_BN;
let totalVotesBalance = ZERO_BN;
const totalDelegatorsObj: IDelegatorsAndDelegatees = {};
const totalDelegateesObj: IDelegatorsAndDelegatees = {};

if (data['data']?.votingDelegations?.length) {
data['data']?.votingDelegations.map((delegation: { lockPeriod: number; balance: string; from: string; to: string }) => {
const bnBalance = new BN(delegation?.balance);
const bnConviction = new BN(delegation?.lockPeriod || 1);
const vote = delegation?.lockPeriod ? bnBalance.mul(bnConviction) : bnBalance.div(new BN('10'));

totalVotesBalance = totalVotesBalance.add(vote);

totalCapital = totalCapital.add(bnBalance);

if (totalDelegateesObj[delegation?.to] === undefined) {
totalDelegateesObj[delegation?.to] = {
count: 1,
data: [{ capital: delegation.balance, from: delegation?.from, lockedPeriod: delegation.lockPeriod || 0.1, to: delegation?.to, votingPower: vote.toString() }]
};
} else {
totalDelegateesObj[delegation?.to] = {
count: totalDelegateesObj[delegation?.to]?.count + 1,
data: [
...(totalDelegateesObj[delegation?.to]?.data || []),
{ capital: delegation.balance, from: delegation?.from, lockedPeriod: delegation.lockPeriod || 0.1, to: delegation?.to, votingPower: vote.toString() }
]
};
}
if (totalDelegatorsObj[delegation?.from] === undefined) {
totalDelegatorsObj[delegation?.from] = {
count: 1,
data: [{ capital: delegation.balance, from: delegation?.from, lockedPeriod: delegation.lockPeriod || 0.1, to: delegation?.to, votingPower: vote.toString() }]
};
} else {
totalDelegatorsObj[delegation?.from] = {
count: totalDelegatorsObj[delegation?.to]?.count + 1,
data: [
...(totalDelegatorsObj[delegation?.to]?.data || []),
{ capital: delegation.balance, from: delegation?.from, lockedPeriod: delegation.lockPeriod || 0.1, to: delegation.to, votingPower: vote.toString() }
]
};
}
});
}

const delegationStats: IDelegationAnalytics = {
delegateesData: totalDelegateesObj,
delegatorsData: totalDelegatorsObj,
totalCapital: totalCapital.toString(),
totalDelegates: Object.keys(totalDelegateesObj)?.length,
totalDelegators: Object.keys(totalDelegatorsObj)?.length,
totalVotesBalance: totalVotesBalance.toString()
};
return {
data: delegationStats,
error: null,
status: 200
};
} catch (error) {
return {
data: null,
error: error || messages.API_FETCH_ERROR,
status: 500
};
}
};

async function handler(req: NextApiRequest, res: NextApiResponse<IDelegationAnalytics | MessageType>) {
storeApiKeyUsage(req);

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

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

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

export default withErrorHandling(handler);
76 changes: 76 additions & 0 deletions pages/api/v1/trackLevelAnalytics/all-track-votes-analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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 { NextApiRequest, NextApiResponse } from 'next';
import storeApiKeyUsage from '~src/api-middlewares/storeApiKeyUsage';
import withErrorHandling from '~src/api-middlewares/withErrorHandling';
import { isValidNetwork } from '~src/api-utils';
import { networkDocRef } from '~src/api-utils/firestore_refs';
import { redisGet, redisSetex } from '~src/auth/redis';
import { MessageType } from '~src/auth/types';
import messages from '~src/auth/utils/messages';
import { IAnalyticsVoteTrends } from '~src/components/TrackLevelAnalytics/types';
import { networkTrackInfo } from '~src/global/post_trackInfo';
import { ProposalType } from '~src/global/proposalType';
import apiErrorWithStatusCode from '~src/util/apiErrorWithStatusCode';
import { generateKey } from '~src/util/getRedisKeys';

const getAllTrackLevelVotesAnalytics = async ({ network }: { network: string }) => {
const TTL_DURATION = 3600 * 23; // 23 Hours or 82800 seconds

try {
if (!network || !isValidNetwork(network)) throw apiErrorWithStatusCode(messages.INVALID_NETWORK, 400);

const trackNumbers = Object.entries(networkTrackInfo[network]).map(([, value]) => value.trackId);
const votes: IAnalyticsVoteTrends[] = [];

const dataPromise = trackNumbers.map(async (trackNumber) => {
const trackSnapshot = await networkDocRef(network).collection('track_level_analytics').doc(String(trackNumber))?.collection('votes').get();

trackSnapshot.docs.map((doc) => {
const data = doc.data();
votes.push(data as IAnalyticsVoteTrends);
});
});

await Promise.allSettled(dataPromise);

if (process.env.IS_CACHING_ALLOWED == '1') {
const redisKey = generateKey({ govType: 'OpenGov', keyType: 'votesAnalytics', network, proposalType: ProposalType.REFERENDUM_V2 });
const redisData = await redisGet(redisKey);

if (redisData) {
return {
data: { votes: JSON.parse(redisData) },
error: null,
status: 200
};
}
}

if (process.env.IS_CACHING_ALLOWED == '1') {
await redisSetex(generateKey({ govType: 'OpenGov', keyType: 'votesAnalytics', network, proposalType: ProposalType.REFERENDUM_V2 }), TTL_DURATION, JSON.stringify(votes));
}
return { data: { votes: votes || [] }, error: null, status: 200 };
} catch (err) {
return { data: null, error: err || messages.API_FETCH_ERROR, status: err.name };
}
};

async function handler(req: NextApiRequest, res: NextApiResponse<{ votes: IAnalyticsVoteTrends[] } | MessageType>) {
storeApiKeyUsage(req);

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

const { data, error } = await getAllTrackLevelVotesAnalytics({
network
});
if (data) {
return res.status(200).json(data);
} else {
return res.status(400).json({ message: error });
}
}

export default withErrorHandling(handler);
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { IDelegationAnalytics, IDelegatorsAndDelegatees } from '~src/redux/track

const ZERO_BN = new BN(0);

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

Expand Down Expand Up @@ -104,7 +104,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<IDelegationAnal

const { trackId } = req.body;

const { data, error } = await getDelegationAnalyticsStats({ network, trackId: Number(trackId) });
const { data, error } = await getTrackDelegationAnalyticsStats({ network, trackId: Number(trackId) });

if (data) {
return res.status(200).json(data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const AnalyticsDelegation = ({ trackId }: { className?: string; trackId: number
const [noData, setNoData] = useState<boolean>(false);

const getData = async () => {
const { data, error } = await nextApiClientFetch<IDelegationAnalytics>('/api/v1/trackLevelAnalytics/delegation-analytics-stats', {
const { data, error } = await nextApiClientFetch<IDelegationAnalytics>('/api/v1/trackLevelAnalytics/track-delegation-analytics-stats', {
trackId
});

Expand Down
2 changes: 1 addition & 1 deletion src/components/TrackLevelAnalytics/TrackAnalyticsStats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const TrackAnalyticsStats: FC<IProps> = (props) => {

const getData = async () => {
setLoading(true);
const { data, error } = await nextApiClientFetch<ITrackAnalyticsStats | MessageType>('/api/v1/trackLevelAnalytics/analytics-stats', {
const { data, error } = await nextApiClientFetch<ITrackAnalyticsStats | MessageType>('/api/v1/trackLevelAnalytics/track-analytics-stats', {
trackId: trackId
});

Expand Down
26 changes: 26 additions & 0 deletions src/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2313,6 +2313,7 @@ export const TOTAL_DELEGATATION_STATS = `query DelegationStats ($type_eq:Delegat
from
to
balance
track
}
}
`;
Expand Down Expand Up @@ -2391,6 +2392,21 @@ diffActiveProposals: proposalsConnection(where: { trackNumber_eq: $track_num, st
totalCount
}
}`;
export const GET_ALL_TRACK_LEVEL_ANALYTICS_STATS = `
query getTrackLevelAnalyticsStats($before: DateTime ="2024-02-01T13:21:30.000000Z") {
diffActiveProposals: proposalsConnection(where: { status_not_in: [Cancelled, TimedOut, Confirmed, Approved, Rejected, Executed, Killed, ExecutionFailed], createdAt_gt:$before }, orderBy: id_ASC){
totalCount
}
diffProposalCount: proposalsConnection(where: { createdAt_gt: $before}, orderBy: id_ASC){
totalCount
}
totalActiveProposals: proposalsConnection(where: {status_not_in: [Cancelled, TimedOut, Confirmed, Approved, Rejected, Executed, Killed, ExecutionFailed] }, orderBy: id_ASC){
totalCount
}
totalProposalCount: proposalsConnection( orderBy: id_ASC){
totalCount
}
}`;

export const GET_TRACK_LEVEL_ANALYTICS_DELEGATION_DATA = `
query DelegationStats ($track_num:Int!){
Expand All @@ -2401,3 +2417,13 @@ query DelegationStats ($track_num:Int!){
lockPeriod
}
}`;

export const GET_ALL_TRACK_LEVEL_ANALYTICS_DELEGATION_DATA = `query DelegationStats{
votingDelegations(where: {endedAtBlock_isNull: true, type_eq:OpenGov}) {
from
to
balance
lockPeriod
}
}
`;

0 comments on commit d5c196a

Please sign in to comment.