polkadot-js/apps

View on GitHub
packages/page-referenda/src/useReferenda.ts

Summary

Maintainability
B
6 hrs
Test Coverage
// Copyright 2017-2024 @polkadot/app-referenda authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { Option } from '@polkadot/types';
import type { BN } from '@polkadot/util';
import type { PalletReferenda, ReferendaGroup, ReferendaGroupKnown, Referendum, TrackDescription } from './types.js';

import { useMemo } from 'react';

import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';

import useReferendaIds from './useReferendaIds.js';
import useTracks from './useTracks.js';
import { calcDecidingEnd, getTrackName, isConvictionVote } from './util.js';

function sortOngoing (a: Referendum, b: Referendum): number {
  const ao = a.info.asOngoing;
  const bo = b.info.asOngoing;

  return ao.track.cmp(bo.track) || (
    ao.deciding.isSome === bo.deciding.isSome
      ? ao.deciding.isSome
        ? a.info.asOngoing.deciding.unwrap().since.cmp(
          b.info.asOngoing.deciding.unwrap().since
        )
        : 0
      : ao.deciding.isSome
        ? -1
        : 1
  );
}

function sortReferenda (a: Referendum, b: Referendum): number {
  return (
    a.info.isOngoing === b.info.isOngoing
      ? a.info.isOngoing
        ? sortOngoing(a, b)
        : 0
      : a.info.isOngoing
        ? -1
        : 1
  ) || b.id.cmp(a.id);
}

function sortGroups (a: ReferendaGroupKnown, b: ReferendaGroupKnown): number {
  return a.trackId && b.trackId
    ? a.trackId.cmp(b.trackId)
    : a.trackId
      ? -1
      : 1;
}

const OPT_MULTI = {
  transform: ([[ids], all]: [[BN[]], Option<Referendum['info']>[]]) =>
    all
      .map((o, i) =>
        o.isSome
          ? [ids[i], o.unwrap()]
          : null
      )
      .filter((r): r is [BN, Referendum['info']] => !!r)
      .map(([id, info]): Referendum => ({
        id,
        info,
        isConvictionVote: isConvictionVote(info),
        key: id.toString()
      })),
  withParamsTransform: true
};

function group (tracks: TrackDescription[], totalIssuance?: BN, referenda?: Referendum[]): ReferendaGroup[] {
  if (!referenda || !totalIssuance) {
    // return an empty group when we have no referenda
    return [{ key: 'empty' }];
  } else if (!tracks) {
    // if we have no tracks, we just return the referenda sorted
    return [{ key: 'referenda', referenda: referenda.sort(sortReferenda) }];
  }

  const grouped: ReferendaGroupKnown[] = [];
  const other: ReferendaGroupKnown = { key: 'referenda', referenda: [] };

  // sort the referenda by track inside groups
  for (let i = 0, count = referenda.length; i < count; i++) {
    const ref = referenda[i];

    // only ongoing have tracks
    const trackInfo = ref.info.isOngoing
      ? tracks.find(({ id }) => id.eq(ref.info.asOngoing.track))
      : undefined;

    if (trackInfo) {
      ref.trackGraph = trackInfo.graph;
      ref.trackId = trackInfo.id;
      ref.track = trackInfo.info;

      if (ref.isConvictionVote && ref.info.isOngoing) {
        const { deciding, tally } = ref.info.asOngoing;

        if (deciding.isSome) {
          const { since } = deciding.unwrap();

          ref.decidingEnd = calcDecidingEnd(totalIssuance, tally, trackInfo.info, since);
        }
      }

      const group = grouped.find(({ track }) => ref.track === track);

      if (!group) {
        // we don't have a group as of yet, create one
        grouped.push({
          key: `track:${ref.trackId.toString()}`,
          referenda: [ref],
          track: ref.track,
          trackGraph: ref.trackGraph,
          trackId: ref.trackId,
          trackName: getTrackName(ref.trackId, ref.track)
        });
      } else {
        // existing group, just add the referendum
        group.referenda.push(ref);
      }
    } else {
      // if we have no track, we just add it to "other"
      other.referenda.push(ref);
    }
  }

  // if we do have items in "other", we add it (or if none, then empty other)
  if ((other.referenda && other.referenda.length !== 0) || !grouped.length) {
    grouped.push(other);
  }

  // sort referenda per group
  for (let i = 0, count = grouped.length; i < count; i++) {
    grouped[i].referenda.sort(sortReferenda);
  }

  // sort all groups
  return grouped.sort(sortGroups);
}

function useReferendaImpl (palletReferenda: PalletReferenda): [ReferendaGroup[], TrackDescription[]] {
  const { api, isApiReady } = useApi();
  const totalIssuance = useCall<BN>(isApiReady && api.query.balances.totalIssuance);
  const ids = useReferendaIds(palletReferenda);
  const tracks = useTracks(palletReferenda);
  const referenda = useCall(isApiReady && ids && ids.length !== 0 && api.query[palletReferenda as 'referenda'].referendumInfoFor.multi, [ids], OPT_MULTI);

  return useMemo(
    () => [
      (ids && ids.length === 0)
        ? [{ key: 'referenda', referenda: [] }]
        : group(tracks, totalIssuance, referenda),
      tracks
    ],
    [ids, referenda, totalIssuance, tracks]
  );
}

export default createNamedHook('useReferenda', useReferendaImpl);