Skip to content

Commit

Permalink
fix: update referendum comments (#1548)
Browse files Browse the repository at this point in the history
Co-authored-by: Nick <[email protected]>
  • Loading branch information
AMIRKHANEF and Nick-1979 authored Sep 28, 2024
1 parent f1a2895 commit da77ba5
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ const Reactions = ({ comment }: {comment: CommentType | Reply;}) => {

const backers =
'comment_reactions' in comment && comment.comment_reactions['👍'].count
? displayUsernames(comment.comment_reactions['👍'].usernames)
? displayUsernames(comment.comment_reactions['👍'].usernames ?? [])
: 'reply_reactions' in comment && comment.reply_reactions['👍'].count
? displayUsernames(comment.reply_reactions['👍'].usernames)
? displayUsernames(comment.reply_reactions['👍'].usernames ?? [])
: '';

const backersCount =
Expand All @@ -53,9 +53,9 @@ const Reactions = ({ comment }: {comment: CommentType | Reply;}) => {

const opposers =
'comment_reactions' in comment && comment.comment_reactions['👎'].count
? displayUsernames(comment.comment_reactions['👎'].usernames)
? displayUsernames(comment.comment_reactions['👎'].usernames ?? [])
: 'reply_reactions' in comment && comment.reply_reactions['👎'].count
? displayUsernames(comment.reply_reactions['👎'].usernames)
? displayUsernames(comment.reply_reactions['👎'].usernames ?? [])
: '';

const opposersCount =
Expand Down Expand Up @@ -127,33 +127,60 @@ const VoteType = ({ comment }: {comment: CommentType | Reply;}) => {
);
};

const EditedTag = ({ comment }: {comment: CommentType | Reply;}) => {
const { t } = useTranslation();
const isEdited = comment.created_at && comment.updated_at && new Date(comment.created_at).getTime() !== new Date(comment.updated_at).getTime();

if (isEdited) {
return (
<Grid item ml='10px'>
<Typography sx={{ color: 'text.disabled', fontSize: '14px', fontWeight: 400, p: '0 10px', textAlign: 'center' }}>
{t('Edited')}
</Typography>
</Grid>
);
} else {
return <></>;
}
};

export default function Comment ({ address, comment, noSource }: CommentProps): React.ReactElement {
const theme = useTheme();
const { api, chain } = useInfo(address);

const hasReactions = useMemo(() => 'comment_reactions' in comment || 'reply_reactions' in comment, [comment]);
const commenterAddress = useMemo(() => comment.proposer && isValidAddress(comment.proposer) ? comment.proposer : undefined, [comment.proposer]);
const { commenterAddress, commenterFormattedAddress } = useMemo(() => {
if (!comment.proposer || !isValidAddress(comment.proposer)) {
return { commenterAddress: undefined, commenterFormattedAddress: undefined };
}

return noSource
? { commenterAddress: comment.proposer, commenterFormattedAddress: undefined } // noSource means it is from PA (Polkassembly) and it returns address as substrate format
: { commenterAddress: undefined, commenterFormattedAddress: comment.proposer }; // means it is from SS (SubSquare) and it returns address as Polkadot/Kusama format
}, [comment.proposer, noSource]);

return (
<Grid alignItems='center' container item sx={{ mb: '10px' }}>
<Grid item maxWidth='50%' width='fit-content'>
<Identity address={commenterAddress} api={api} chain={chain} identiconSize={25} name={comment?.username ?? undefined} noIdenticon={!commenterAddress} showShortAddress showSocial={false} style={{ fontSize: '14px', fontWeight: 400, lineHeight: '47px', maxWidth: '100%', minWidth: '35%', width: 'fit-content' }} />
<Identity address={commenterAddress} api={api} chain={chain} formatted={commenterFormattedAddress} identiconSize={25} name={comment?.username ?? undefined} noIdenticon={!commenterAddress} showShortAddress showSocial={false} style={{ fontSize: '14px', fontWeight: 400, lineHeight: '47px', maxWidth: '100%', minWidth: '35%', width: 'fit-content' }} />
</Grid>
<Grid item width='fit-content'>
<VoteType
comment ={comment}
comment={comment}
/>
</Grid>
<Grid item sx={{ color: 'text.disabled', fontSize: '16px', px: '15px' }}>
{formatRelativeTime(comment.created_at)}
</Grid>
{!noSource &&
{
<Grid item>
<Typography sx={{ border: `0.01px solid ${theme.palette.text.disabled}`, borderRadius: '30px', fontSize: '14px', fontWeight: 400, p: '0 10px', textAlign: 'center' }}>
{'Polkassembly'}
{noSource && 'Polkassembly'}
{!noSource && 'SubSquare'}
</Typography>
</Grid>
}
<EditedTag comment={comment} />
<Grid item sx={{ pl: '25px' }} xs={12}>
{comment?.content &&
<ReactMarkdown
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default function Comments ({ address, referendum }: CommentsProps): React
<Grid container item xs={12}>
{sortedComments?.map((comment, index) => (
<Grid container key={index} sx={{ borderBottom: index !== sortedComments.length - 1 ? `0.01px solid ${theme.palette.text.disabled}` : undefined }}>
<CommentView address={address ?? ''} comment={comment} />
<CommentView address={address ?? ''} comment={comment} noSource={comment?.commentSource !== 'SS'} />
{!!comment?.replies?.length &&
<Replies address={address} replies={comment?.replies} />
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default function Replies ({ address, replies }: { address: string | undef
</Grid>
{expanded && replies?.map((reply, index) => (
<Grid container key={index} pl='5px' pt='5px'>
<CommentView address={address} comment={reply} noSource />
<CommentView address={address} comment={reply} noSource={reply?.commentSource !== 'SS'} />
</Grid>
))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import type { AccountId32 } from '@polkadot/types/interfaces/runtime';
import type { BN } from '@polkadot/util';
import type { LatestReferenda, Origins, Referendum, ReferendumPA, ReferendumSb, TopMenu } from './types';
import type { CommentType, LatestReferenda, Origins, Referendum, ReferendumPA, ReferendumSb, Reply, TopMenu } from './types';

import { postData } from '../../../util/api';
import { FINISHED_REFERENDUM_STATUSES, TRACK_LIMIT_TO_LOAD_PER_REQUEST } from './consts';
Expand All @@ -25,6 +25,85 @@ export interface Statistics {
'OriginsCount': number
}


interface DataSS {
items: CommentItemSS[];
page: number;
pageSize: number;
total: number;
}

interface VoteSS {
account: string;
aye: boolean;
balance: string;
conviction: number;
delegations: {
votes: string;
capital: string;
};
isDelegating: boolean;
isSplit: boolean;
isSplitAbstain: boolean;
isStandard: boolean;
queryAt: number;
referendumIndex: number;
votes: string;
}

interface CommentItemSS {
author: {
username: string;
cid: string;
};
content: string;
contentType: string;
contentVersion: string;
createdAt: string;
dataSource: string;
height: number;
proposer: string;
reactions: ReactionSS[];
referendaReferendum: string;
replies: ReplySS[];
updatedAt: string;
_id: string;
}

interface ReplySS {
_id: string;
referendaReferendum: string;
replyToComment: string;
content: string;
contentType: string;
contentVersion: string;
author: {
username: string;
publicKey: string;
address: string;
};
height: number;
createdAt: string;
updatedAt: string;
dataSource: string;
cid: string;
proposer: string;
reactions: ReactionSS[];
}

interface ReactionSS {
_id: string,
comment: string,
dataSource: string,
proposer: string,
cid: string,
createdAt: string,
parentCid: string,
reaction: number,
updatedAt: string,
user: null
}

export interface VoteType {
decision: string;
voter: string;
Expand Down Expand Up @@ -75,7 +154,7 @@ export interface FilteredVotes {

export const isFinished = (referendum: Referendum | undefined) => referendum?.status ? FINISHED_REFERENDUM_STATUSES.includes(referendum.status) : undefined;

export async function getReferendumStatistics(chainName: string, type: 'referenda' | 'fellowship'): Promise<Statistics | null> {
export async function getReferendumStatistics (chainName: string, type: 'referenda' | 'fellowship'): Promise<Statistics | null> {
// console.log('Getting ref stat from sb ... ');

return new Promise((resolve) => {
Expand All @@ -102,7 +181,7 @@ export async function getReferendumStatistics(chainName: string, type: 'referend
});
}

export async function getReferendumVotesFromSubscan(chainName: string, referendumIndex: number | undefined): Promise<string | null> {
export async function getReferendumVotesFromSubscan (chainName: string, referendumIndex: number | undefined): Promise<string | null> {
if (!referendumIndex) {
console.log('referendumIndex is undefined while getting Referendum Votes from Sb ');

Expand Down Expand Up @@ -135,7 +214,7 @@ export async function getReferendumVotesFromSubscan(chainName: string, referendu
});
}

export async function getLatestReferendums(chainName: string, listingLimit = 30): Promise<LatestReferenda[] | null> {
export async function getLatestReferendums (chainName: string, listingLimit = 30): Promise<LatestReferenda[] | null> {
// console.log(`Getting Latest referendum on ${chainName} from PA ...`);

const requestOptions = {
Expand Down Expand Up @@ -164,7 +243,7 @@ export async function getLatestReferendums(chainName: string, listingLimit = 30)
});
}

export async function getAllVotesFromPA(chainName: string, refIndex: number, listingLimit = 100, isFellowship: boolean | undefined): Promise<AllVotesType | null> {
export async function getAllVotesFromPA (chainName: string, refIndex: number, listingLimit = 100, isFellowship: boolean | undefined): Promise<AllVotesType | null> {
// console.log(`Getting All Votes on ${chainName} for refIndex: ${refIndex} from PA ...`);

const requestOptions = {
Expand Down Expand Up @@ -192,7 +271,7 @@ export async function getAllVotesFromPA(chainName: string, refIndex: number, lis
});
}

export async function getTrackOrFellowshipReferendumsPA(chainName: string, page = 1, track?: number): Promise<LatestReferenda[] | null> {
export async function getTrackOrFellowshipReferendumsPA (chainName: string, page = 1, track?: number): Promise<LatestReferenda[] | null> {
console.log(`Getting refs on ${chainName} track:${track} from PA`);

const requestOptions = {
Expand Down Expand Up @@ -221,7 +300,7 @@ export async function getTrackOrFellowshipReferendumsPA(chainName: string, page
});
}

export async function getReferendumPA(chainName: string, type: TopMenu, postId: number): Promise<ReferendumPA | null> {
export async function getReferendumPA (chainName: string, type: TopMenu, postId: number): Promise<ReferendumPA | null> {
// console.log(`Getting ref #${postId} info with type:${type} on chain:${chainName} from PA ...`);

const requestOptions = {
Expand Down Expand Up @@ -252,7 +331,7 @@ export async function getReferendumPA(chainName: string, type: TopMenu, postId:
});
}

export async function getReferendumSb(chainName: string, type: TopMenu, postId: number): Promise<ReferendumSb | null> {
export async function getReferendumSb (chainName: string, type: TopMenu, postId: number): Promise<ReferendumSb | null> {
// console.log(`Getting ref #${postId} info from sb ...`);

// Convert postId to uint
Expand Down Expand Up @@ -313,7 +392,7 @@ interface RefListSb {
}[];
}

export async function getReferendumsListSb(chainName: string, type: TopMenu, listingLimit = 30): Promise<RefListSb | null> {
export async function getReferendumsListSb (chainName: string, type: TopMenu, listingLimit = 30): Promise<RefListSb | null> {
console.log('Getting ref list from sb ...');

return new Promise((resolve) => {
Expand Down Expand Up @@ -342,3 +421,78 @@ export async function getReferendumsListSb(chainName: string, type: TopMenu, lis
}
});
}

/**
* Fetches and formats comments for a specific referendum from SubSquare.
* @param chainName The name of the blockchain.
* @param refId The ID of the referendum.
* @returns A promise that resolves to an array of formatted comments or null if an error occurs.
*/
export async function getReferendumCommentsSS (chainName: string, refId: string | number): Promise<CommentType[] | null> {
// console.log(`Getting comments of ref ${refId} from SS ...`);

try {
// Fetch both comments and votes concurrently
const [commentsResponse, votesResponse] = await Promise.all([
fetch(`https://${chainName}.subsquare.io/api/gov2/referendums/${refId}/comments`),
fetch(`https://${chainName}.subsquare.io/api/gov2/referenda/${refId}/votes`)
]);

const comments = await commentsResponse.json() as DataSS;
const votes = await votesResponse.json() as VoteSS[];

// Helper function to determine the vote decision
const voteInformation = (address: string): string => {
const vote = votes.find(({ account }) => account === address);

if (vote?.aye) {
return 'yes';
}

if (!vote?.aye && (vote?.isSplit || vote?.isSplitAbstain)) {
return 'Abstain';
}

return 'no';
};

// Format the comments
const formattedComments = comments.items.map(({ _id, author, content, createdAt, proposer, reactions, replies, updatedAt }) => ({
commentSource: 'SS',
comment_reactions: {
'👍': { count: reactions.length, usernames: reactions[0]?.user },
'👎': { count: 0, usernames: undefined } // SubSquare does not display dislikes
},
content,
created_at: createdAt,
id: _id,
proposer,
// Format replies
replies: replies.map(({ _id, cid, content, createdAt, proposer, reactions, updatedAt }) => ({
commentSource: 'SS',
content,
created_at: createdAt,
id: _id,
proposer,
reply_reactions: {
'👍': { count: reactions.length, usernames: reactions[0]?.user },
'👎': { count: 0, usernames: undefined } // SubSquare does not display dislikes
},
updated_at: updatedAt,
user_id: cid,
username: ''
} as unknown as Reply)),
sentiment: 0,
updated_at: updatedAt,
user_id: author.cid,
username: '',
votes: [{ decision: voteInformation(proposer) }]
} as unknown as CommentType));

return formattedComments;
} catch (error) {
console.error(`Error in getReferendumCommentsSS for chain ${chainName}, referendum ${refId}:`, error);

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ export type Origins = 'root' | 'whitelisted_caller' | 'staking_admin' | 'treasur
export interface Reply {
reply_reactions: Reaction;
content: string;
created_at: Date,
id: string,
proposer: string,
updated_at: Date,
user_id: number,
username: string
created_at: Date;
id: string;
proposer: string;
updated_at: Date;
user_id: number;
username: string;
commentSource?: 'SS' | 'PA'; // SS --> SubSquare, PA --> Polkassembly
}

interface Reaction {
Expand All @@ -43,6 +44,7 @@ export interface CommentType {
user_id: number;
username: string;
votes?: { decision?: 'yes' | 'no' | 'abstain' }[];
commentSource?: 'SS' | 'PA'; // SS --> SubSquare, PA --> Polkassembly
}

export interface ReferendumHistory {
Expand Down
Loading

0 comments on commit da77ba5

Please sign in to comment.