From f48d0b65919ac1fc7b7d693c31b2090e554d36a4 Mon Sep 17 00:00:00 2001 From: Mikhail <3889895+vindi-r@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:39:34 -0300 Subject: [PATCH] feat: add next leader to the table --- solfees-fe/src/common/isReal.ts | 8 ++++ solfees-fe/src/common/prepareValidatorRow.ts | 29 ++++++++--- .../components/layout/NextSlotInformer.tsx | 27 ----------- solfees-fe/src/components/ui/ComputeUnits.tsx | 48 ++++++++++++------- solfees-fe/src/components/ui/EarnedSol.tsx | 21 +++++--- solfees-fe/src/components/ui/SimpleCell.tsx | 22 ++++++--- solfees-fe/src/components/ui/Slots.tsx | 21 ++++---- solfees-fe/src/components/ui/Transactions.tsx | 18 ++++--- solfees-fe/src/components/ui/Validator.tsx | 8 +++- solfees-fe/src/pages/HomeNew.tsx | 47 ++++++++++++------ solfees-fe/src/pages/HomeOld.tsx | 1 + solfees-fe/src/store/websocketStore.ts | 6 +-- 12 files changed, 159 insertions(+), 97 deletions(-) create mode 100644 solfees-fe/src/common/isReal.ts delete mode 100644 solfees-fe/src/components/layout/NextSlotInformer.tsx diff --git a/solfees-fe/src/common/isReal.ts b/solfees-fe/src/common/isReal.ts new file mode 100644 index 0000000..62b0e94 --- /dev/null +++ b/solfees-fe/src/common/isReal.ts @@ -0,0 +1,8 @@ +import { CustomRow, CustomRowBasic } from "./prepareValidatorRow.ts"; + +// TODO probably we should change CommitmentStatus to Enum and compare as if(elt.commitment in CommitmentStatus) to find extended statuses +export function isReal( + slot: CustomRow["slots"][number] | CustomRowBasic["slots"][number] +): slot is CustomRowBasic["slots"][number] { + return slot.commitment !== "next-leader" && slot.commitment !== "scheduled"; +} diff --git a/solfees-fe/src/common/prepareValidatorRow.ts b/solfees-fe/src/common/prepareValidatorRow.ts index 7ba22fd..6c07bb4 100644 --- a/solfees-fe/src/common/prepareValidatorRow.ts +++ b/solfees-fe/src/common/prepareValidatorRow.ts @@ -1,10 +1,15 @@ import { CommitmentStatus, SlotContent } from "../store/websocketStore.ts"; -export type CustomRow = { +export type ExtendedCommitmentStatus = CommitmentStatus | "next-leader" | "scheduled"; +type ExtendedSlotContent = Omit & { + commitment: ExtendedCommitmentStatus; +}; + +export type CustomRowBasic = { id: string; leader: string; slots: { - commitment: CommitmentStatus; + commitment: ExtendedCommitmentStatus; slot: number; }[]; transactions: { @@ -22,15 +27,18 @@ export type CustomRow = { fee1: number[]; fee2: number[]; }; +export type CustomRow = Omit & { + slots: { slot: number; commitment: ExtendedCommitmentStatus }[]; +}; type FxType = (arg: [id: string, slots: SlotContent[]]) => CustomRow; -const getFakeSlot = (leader: string, newSlotId: number): SlotContent => ({ +export const getFakeSlot = (leader: string, newSlotId: number): ExtendedSlotContent => ({ leader, slot: newSlotId, totalTransactionsFiltered: 0, feeLevels: [0, 0, 0], feeAverage: 0, - commitment: "fake" as "confirmed", + commitment: "scheduled", totalUnitsConsumed: 0, totalFee: 0, hash: "", @@ -40,7 +48,7 @@ const getFakeSlot = (leader: string, newSlotId: number): SlotContent => ({ time: 0, }); -function fillGaps(list: SlotContent[]): SlotContent[] { +function fillGaps(list: SlotContent[]): ExtendedSlotContent[] { if (!list.length) return list; if (list.length === 4) return list; const groupIdx = ((list?.[0]?.slot || 0) / 4) | 0; @@ -51,7 +59,7 @@ function fillGaps(list: SlotContent[]): SlotContent[] { const isFourth = list.find((elt) => (elt.slot / 4) % 1 === 0.75); const newLeader = list?.[0]?.leader || "UNKNOWN"; - const newList: typeof list = []; + const newList: ExtendedSlotContent[] = []; isFirst ? newList.push({ ...isFirst }) : newList.push(getFakeSlot(newLeader, groupIdx * 4)); isSecond ? newList.push({ ...isSecond }) : newList.push(getFakeSlot(newLeader, groupIdx * 4 + 1)); isThird ? newList.push({ ...isThird }) : newList.push(getFakeSlot(newLeader, groupIdx * 4 + 2)); @@ -66,6 +74,13 @@ function fillGaps(list: SlotContent[]): SlotContent[] { export const prepareValidatorRow: FxType = ([id, rawSlots]) => { const slots = fillGaps(rawSlots); const leader = slots[0]?.leader || "UNKNOWN"; + return prepareSingeRow(id, leader, slots); +}; +export function prepareSingeRow( + id: string, + leader: string, + slots: ExtendedSlotContent[] +): CustomRow { return { id, leader, @@ -97,4 +112,4 @@ export const prepareValidatorRow: FxType = ([id, rawSlots]) => { fee1: slots.map((elt) => elt.feeLevels[1] || 0), fee2: slots.map((elt) => elt.feeLevels[2] || 0), }; -}; +} diff --git a/solfees-fe/src/components/layout/NextSlotInformer.tsx b/solfees-fe/src/components/layout/NextSlotInformer.tsx deleted file mode 100644 index c2bba4a..0000000 --- a/solfees-fe/src/components/layout/NextSlotInformer.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useWebSocketStore } from "../../store/websocketStore.ts"; -import { useScheduleStore } from "../../store/scheduleStore.ts"; -import { useShallow } from "zustand/react/shallow"; -import { useMemo } from "react"; - -export const NextSlotInformer = () => { - const slots2 = useWebSocketStore(useShallow((state) => state.slots2)); - const indices = useScheduleStore(useShallow((state) => state.indices)); - const leaders = useScheduleStore(useShallow((state) => state.leaders)); - - const lastSlot = useMemo(() => { - const idx = Math.max(...Object.keys(slots2).map(Number)); - return slots2[idx]?.[0]?.slot || 0; - }, [slots2]); - - const groupIndex = indices[lastSlot % 432_000] || 0; - const nextLeader = leaders[groupIndex] || ""; - const nextSlot = (((lastSlot / 4) | 0) + 1) * 4; - - if (!lastSlot) return null; - return ( -
-
{nextLeader}
-
{nextSlot}
-
- ); -}; diff --git a/solfees-fe/src/components/ui/ComputeUnits.tsx b/solfees-fe/src/components/ui/ComputeUnits.tsx index bdb9467..3d0cec9 100644 --- a/solfees-fe/src/components/ui/ComputeUnits.tsx +++ b/solfees-fe/src/components/ui/ComputeUnits.tsx @@ -1,9 +1,11 @@ import { Text } from "@consta/uikit/Text"; import { CustomRow } from "../../common/prepareValidatorRow.ts"; import { withTooltip } from "@consta/uikit/withTooltip"; +import { isReal } from "../../common/isReal.ts"; interface Props { items: CustomRow["computeUnits"]; + slots: CustomRow["slots"]; } const TextWithTooltip = withTooltip({ content: "Top tooltip" })(Text); @@ -26,25 +28,39 @@ const amountFormatter = (amount: number): string => { return result; }; -export const ComputeUnits = ({ items }: Props) => { +export const ComputeUnits = ({ items, slots }: Props) => { return (
- {items.map((elt, idx) => ( -
- { + const currentSlot = slots[idx]; + const isFilled = currentSlot ? isReal(currentSlot) : false; + + return ( +
- {amountFormatter(elt.amount)} - ({percentFormatter.format(elt.percent).replace(/,/g, " ")}) - -
- ))} + {isFilled ? ( + + {amountFormatter(elt.amount)} + ({percentFormatter.format(elt.percent).replace(/,/g, " ")}) + + ) : ( + + {" "} + + )} +
+ ); + })}
); }; diff --git a/solfees-fe/src/components/ui/EarnedSol.tsx b/solfees-fe/src/components/ui/EarnedSol.tsx index d2e4a0f..26d4126 100644 --- a/solfees-fe/src/components/ui/EarnedSol.tsx +++ b/solfees-fe/src/components/ui/EarnedSol.tsx @@ -1,8 +1,10 @@ import { Text } from "@consta/uikit/Text"; import { CustomRow } from "../../common/prepareValidatorRow.ts"; +import { isReal } from "../../common/isReal.ts"; interface Props { - list: CustomRow["earnedSol"]; + items: CustomRow["earnedSol"]; + slots: CustomRow["slots"]; } function formatValue(value: number): string { @@ -15,14 +17,19 @@ function formatValue(value: number): string { .replace(/\s/g, "") : value.toFixed(9); } -export const EarnedSol = ({ list }: Props) => { +export const EarnedSol = ({ items, slots }: Props) => { return (
- {list.map((elt, idx) => ( - - {formatValue(elt)} - - ))} + {items.map((elt, idx) => { + const currentSlot = slots[idx]; + const isFilled = currentSlot ? isReal(currentSlot) : false; + + return ( + + {isFilled ? formatValue(elt) : " "} + + ); + })}
); }; diff --git a/solfees-fe/src/components/ui/SimpleCell.tsx b/solfees-fe/src/components/ui/SimpleCell.tsx index 07a8fc6..a6c331e 100644 --- a/solfees-fe/src/components/ui/SimpleCell.tsx +++ b/solfees-fe/src/components/ui/SimpleCell.tsx @@ -1,16 +1,24 @@ import { Text } from "@consta/uikit/Text"; +import { CustomRow } from "../../common/prepareValidatorRow.ts"; +import { isReal } from "../../common/isReal.ts"; interface Props { - list: number[]; + items: number[]; + slots: CustomRow["slots"]; } -export const SimpleCell = ({ list }: Props) => { +export const SimpleCell = ({ items, slots }: Props) => { return (
- {list.map((elt, idx) => ( - - {elt.toLocaleString("en-US", { maximumFractionDigits: 2 })} - - ))} + {items.map((elt, idx) => { + const currentSlot = slots[idx]; + const isFilled = currentSlot ? isReal(currentSlot) : false; + + return ( + + {isFilled ? elt.toLocaleString("en-US", { maximumFractionDigits: 2 }) : " "} + + ); + })}
); }; diff --git a/solfees-fe/src/components/ui/Slots.tsx b/solfees-fe/src/components/ui/Slots.tsx index c596ebd..3aca404 100644 --- a/solfees-fe/src/components/ui/Slots.tsx +++ b/solfees-fe/src/components/ui/Slots.tsx @@ -9,21 +9,26 @@ import { IconCopy } from "@consta/icons/IconCopy"; import { IconWarning } from "@consta/icons/IconWarning"; import { IconProcessing } from "@consta/icons/IconProcessing"; import { IconWatchStroked } from "@consta/icons/IconWatchStroked"; +import { CustomRow, ExtendedCommitmentStatus } from "../../common/prepareValidatorRow.ts"; +import { IconLoading } from "@consta/icons/IconLoading"; interface Props { - items: { - commitment: CommitmentStatus; - slot: number; - }[]; + items: CustomRow["slots"]; } type ComProps = { - value: CommitmentStatus; + value: CustomRow["slots"][number]["commitment"]; }; -const statuses: (CommitmentStatus | "fake")[] = ["processed", "confirmed", "finalized", "fake"]; -const colors: IconPropView[] = ["link", "primary", "success", "ghost"]; -const icons = [IconProcessing, IconCheck, IconAllDone, IconWatchStroked]; +const statuses: ExtendedCommitmentStatus[] = [ + "processed", + "confirmed", + "finalized", + "scheduled", + "next-leader", +]; +const colors: IconPropView[] = ["link", "primary", "success", "ghost", "disabled"]; +const icons = [IconProcessing, IconCheck, IconAllDone, IconLoading, IconWatchStroked]; export const AnimateIconBaseIcons = ({ value }: ComProps) => { const idx = statuses.findIndex((elt) => elt === value); return ( diff --git a/solfees-fe/src/components/ui/Transactions.tsx b/solfees-fe/src/components/ui/Transactions.tsx index ba0dd92..64b9d40 100644 --- a/solfees-fe/src/components/ui/Transactions.tsx +++ b/solfees-fe/src/components/ui/Transactions.tsx @@ -2,9 +2,11 @@ import { Text } from "@consta/uikit/Text"; import { useWebSocketStore } from "../../store/websocketStore.ts"; import { useShallow } from "zustand/react/shallow"; import { CustomRow } from "../../common/prepareValidatorRow.ts"; +import { isReal } from "../../common/isReal.ts"; interface Props { items: CustomRow["transactions"]; + slots: CustomRow["slots"]; } function buildTransactions(slots: CustomRow["transactions"], withFiltered = false) { @@ -32,17 +34,21 @@ function buildTransactions(slots: CustomRow["transactions"], withFiltered = fals return alignedWithKeys; } -export const Transactions = ({ items }: Props) => { +export const Transactions = ({ items, slots }: Props) => { const hasRW = useWebSocketStore(useShallow((state) => !!state.readwriteKeys.length)); const hasRO = useWebSocketStore(useShallow((state) => !!state.readonlyKeys.length)); return (
- {buildTransactions(items, hasRO || hasRW).map((elt) => ( - - {elt.value} - - ))} + {buildTransactions(items, hasRO || hasRW).map((elt, idx) => { + const currentSlot = slots[idx]; + const isFilled = currentSlot ? isReal(currentSlot) : false; + return ( + + {isFilled ? elt.value : " "} + + ); + })}
); }; diff --git a/solfees-fe/src/components/ui/Validator.tsx b/solfees-fe/src/components/ui/Validator.tsx index d149ef1..54bd45c 100644 --- a/solfees-fe/src/components/ui/Validator.tsx +++ b/solfees-fe/src/components/ui/Validator.tsx @@ -1,10 +1,14 @@ +import { CustomRow } from "../../common/prepareValidatorRow.ts"; + interface Props { leader: string; + slots: CustomRow["slots"]; } -export const Validator = ({ leader }: Props) => { +export const Validator = ({ leader, slots }: Props) => { + const isNext = slots.some((elt) => elt.commitment === "next-leader"); return (
-

Validator Name:

+

{isNext ? "Next" : ""} Validator Name:

{ - const slots2 = useWebSocketStore((state) => state.slots2); - const percents = useWebSocketStore((state) => state.percents); + const slots2 = useWebSocketStore(useShallow((state) => state.slots2)); + const percents = useWebSocketStore(useShallow((state) => state.percents)); + const indices = useScheduleStore(useShallow((state) => state.indices)); + const leaders = useScheduleStore(useShallow((state) => state.leaders)); const memoFee0 = useCallback(() => onEditFee(0), [onEditFee]); const memoFee1 = useCallback(() => onEditFee(1), [onEditFee]); @@ -60,8 +67,21 @@ const CustomTable = ({ onEditFee, onEditKeys }: TableProps) => { const result = Object.entries(unsorted) .sort((a, b) => Number(a[0]) - Number(b[0])) .map(prepareValidatorRow); + // we always add next leader, but pick only one slot for it + const idx = Math.max(...Object.keys(slots2).map(Number)); + const lastSlot = slots2[idx]?.[0]?.slot || 0; + if (lastSlot) { + const nextSlotNumber = (((lastSlot / 4) | 0) + 1) * 4; + const nextLeaderIndex = indices[nextSlotNumber % 432_000] || 0; + const nextLeader = leaders[nextLeaderIndex] || ""; + + const nextSlotContent = getFakeSlot(nextLeader, nextSlotNumber); + nextSlotContent.commitment = "next-leader"; + const nextRow = prepareSingeRow(`scheduled-${nextSlotNumber}`, nextLeader, [nextSlotContent]); + result.push(nextRow); + } return [...result].reverse(); - }, [slots2]); + }, [slots2, indices, leaders]); const columns: TableColumn<(typeof rowsFromSocket2)[number]>[] = useMemo(() => { return [ @@ -69,7 +89,7 @@ const CustomTable = ({ onEditFee, onEditKeys }: TableProps) => { minWidth: 150, title: "Validator", accessor: "leader", - renderCell: ({ row }) => , + renderCell: ({ row }) => , }, { minWidth: 170, @@ -86,7 +106,7 @@ const CustomTable = ({ onEditFee, onEditKeys }: TableProps) => { {title} ), - renderCell: ({ row }) => , + renderCell: ({ row }) => , }, { title: "Compute Units", @@ -99,7 +119,7 @@ const CustomTable = ({ onEditFee, onEditKeys }: TableProps) => { {title} ), - renderCell: ({ row }) => , + renderCell: ({ row }) => , }, { minWidth: 160, @@ -112,7 +132,7 @@ const CustomTable = ({ onEditFee, onEditKeys }: TableProps) => { {title} ), - renderCell: ({ row }) => , + renderCell: ({ row }) => , }, { minWidth: 160, @@ -126,7 +146,7 @@ const CustomTable = ({ onEditFee, onEditKeys }: TableProps) => { {title} ), - renderCell: ({ row }) => , + renderCell: ({ row }) => , }, { minWidth: 160, @@ -148,7 +168,7 @@ const CustomTable = ({ onEditFee, onEditKeys }: TableProps) => { {title} ), - renderCell: ({ row }) => , + renderCell: ({ row }) => , }, { minWidth: 160, @@ -170,7 +190,7 @@ const CustomTable = ({ onEditFee, onEditKeys }: TableProps) => { {title} ), - renderCell: ({ row }) => , + renderCell: ({ row }) => , }, { minWidth: 160, @@ -192,7 +212,7 @@ const CustomTable = ({ onEditFee, onEditKeys }: TableProps) => { {title} ), - renderCell: ({ row }) => , + renderCell: ({ row }) => , }, ]; }, [onEditKeys, percents, memoFee0, memoFee1, memoFee2]); @@ -304,7 +324,6 @@ export const HomeNew = (): FunctionComponent => {
- ((set, get: () => WebSock const slots2 = get().slots2; if (slots2[groupIdx]) { - slots2[groupIdx] = [...slots2[groupIdx], update] + slots2[groupIdx] = [...(slots2[groupIdx] as SlotContent[]), update] .sort((a, b) => b.slot - a.slot) .filter((elt, idx, arr) => { // probably I have dupes because I enqueue a lot of stuff without filtering @@ -85,7 +85,7 @@ export const useWebSocketStore = create((set, get: () => WebSock return sameIdx === idx; }); - slots2[groupIdx] = [...slots2[groupIdx], update] + slots2[groupIdx] = [...(slots2[groupIdx] as SlotContent[]), update] .sort((a, b) => b.slot - a.slot) .filter((elt, idx, arr) => { // probably I have dupes because I enqueue a lot of stuff without filtering @@ -120,7 +120,7 @@ export const useWebSocketStore = create((set, get: () => WebSock } // test - const newSlots = slots2[groupIdx].map((elt) => { + const newSlots = (slots2[groupIdx] as SlotContent[]).map((elt) => { if (elt.slot === update.slot) return { ...elt, ...update }; return elt; });