Sfoglia il codice sorgente

events explorer + Modal

Joystream Stats 2 anni fa
parent
commit
fcd8a8ba21

+ 41 - 14
src/App.tsx

@@ -4,13 +4,8 @@ import "./index.css";
 import { Modals, Routes, Loading, Footer, Status } from "./components";
 
 import * as get from "./lib/getters";
-import { bootstrap, getTokenomics, queryJstats } from "./lib/queries";
-import {
-  //  updateElection,
-  getCouncilApplicants,
-  getCouncilSize,
-  getVotes,
-} from "./lib/election";
+import { getTokenomics, queryJstats } from "./lib/queries";
+import { getCouncilApplicants, getCouncilSize, getVotes } from "./lib/election";
 import {
   getStashes,
   getNominators,
@@ -253,6 +248,7 @@ class App extends React.Component<IProps, IState> {
     return (
       <>
         <Routes
+          selectEvent={this.selectEvent}
           toggleEditKpi={this.toggleEditKpi}
           toggleFooter={this.toggleFooter}
           toggleStar={this.toggleStar}
@@ -261,6 +257,7 @@ class App extends React.Component<IProps, IState> {
         />
 
         <Modals
+          selectEvent={this.selectEvent}
           toggleEditKpi={this.toggleEditKpi}
           toggleShowStatus={this.toggleShowStatus}
           {...this.state}
@@ -278,21 +275,27 @@ class App extends React.Component<IProps, IState> {
   }
 
   // startup from bottom up
+  selectEvent(selectedEvent) {
+    this.setState({ selectedEvent });
+  }
+
   async handleBlock(api: ApiPromise, header: Header) {
-    let { blocks = [], status } = this.state;
+    let { status } = this.state;
     const id = header.number.toNumber();
     const isEven = id / 50 === Math.floor(id / 50);
     if (isEven || status.block?.id + 50 < id) this.updateStatus(api, id);
-    if (blocks.find((b) => b.id === id)) return;
+    if (this.state.blocks.find((b) => b.id === id)) return;
     const timestamp = (await api.query.timestamp.now()).toNumber();
     const duration = status.block ? timestamp - status.block.timestamp : 6000;
     const hash = await getBlockHash(api, id);
-    const events = await getEvents(api, hash);
-
+    const events = (await getEvents(api, hash)).map((e) => {
+      const { section, method, data } = e.event;
+      return { blockId: id, section, method, data: data.toHuman() };
+    });
     status.block = { id, timestamp, duration, events };
     console.debug(`new finalized head`, status.block);
     this.save("status", status);
-    this.setState({ blocks: blocks.concat(status.block) });
+    this.save("blocks", this.state.blocks.concat(status.block));
   }
 
   connectApi() {
@@ -314,9 +317,32 @@ class App extends React.Component<IProps, IState> {
       api.rpc.chain.subscribeFinalizedHeads((header: Header) =>
         this.handleBlock(api, header)
       );
+      this.syncBlocks(api)
     });
   }
 
+  async syncBlocks(api:ApiPromise) {
+    const syncAll = true
+    const head = this.state.blocks.reduce((max, b) => b.id > max ? b.id : max, 0)
+    console.log(`Syncing block events from ${head}`)
+
+    for (let id = head ; id > 0 ; --id) {
+      if (!syncAll) return
+      if (this.state.blocks.find(block=> block.id === id)) continue
+      
+      const hash = await getBlockHash(api, id);
+      const events = (await getEvents(api, hash)).map((e) => {
+        const { section, method, data } = e.event;
+        return { blockId: id, section, method, data: data.toHuman() };
+      });
+      const timestamp = (await api.query.timestamp.now.at(hash)).toNumber();
+      const duration = 6000 // TODO update later
+      const block = { id, timestamp, duration, events };
+      console.debug(`synced block`, block);
+      this.save("blocks", this.state.blocks.concat(block));
+    }
+  }
+
   save(key: string, data: any) {
     this.setState({ [key]: data });
     const value = JSON.stringify(data);
@@ -346,11 +372,11 @@ class App extends React.Component<IProps, IState> {
 
   async loadData() {
     console.debug(`Loading data`);
-    "status members assets providers councils council election workers categories channels proposals posts threads openings tokenomics transactions reports validators nominators staches stakes rewardPoints stars"
+    "status members assets providers councils council election workers categories channels proposals posts threads openings tokenomics transactions reports validators nominators staches stakes rewardPoints stars blocks"
       .split(" ")
       .map((key) => this.load(key));
     getTokenomics().then((tokenomics) => this.save(`tokenomics`, tokenomics));
-    bootstrap(this.save); // axios requests
+    //bootstrap(this.save); // axios requests
     this.updateCouncils();
   }
 
@@ -368,6 +394,7 @@ class App extends React.Component<IProps, IState> {
     this.toggleFooter = this.toggleFooter.bind(this);
     this.toggleShowStatus = this.toggleShowStatus.bind(this);
     this.getMember = this.getMember.bind(this);
+    this.selectEvent = this.selectEvent.bind(this);
   }
 }
 

+ 3 - 84
src/components/Dashboard/index.tsx

@@ -1,92 +1,11 @@
-import React from "react";
-import Council from "./Council";
-import Forum from "./Forum";
-import Proposals from "./Proposals";
-import Validators from "../Validators";
-import Openings from "../Openings";
-import { IState } from "../../types";
-import { Container, Grid } from "@material-ui/core";
+import Events from '../Events'
 
 interface IProps extends IState {
-  toggleStar: (a: string) => void;
-  toggleFooter: () => void;
+  blocks: { id: number; events: any }[];
 }
 
 const Dashboard = (props: IProps) => {
-  const {
-    getMember,
-    toggleStar,
-    councils,
-    members,
-    nominators,
-    openings,
-    posts,
-    proposals,
-    rewardPoints,
-    threads,
-    tokenomics,
-    status,
-    stars,
-    stashes,
-    stakes,
-    validators,
-    domain,
-  } = props;
-
-  return (
-    <div style={{ flexGrow: 1 }}>
-      <Container maxWidth="xl">
-        <Grid container spacing={3}>
-          <Proposals
-            fetchProposals={props.fetchProposals}
-            block={status.block ? status.block.id : 0}
-            members={members}
-            councils={councils}
-            posts={posts}
-            proposals={proposals}
-            proposalPosts={props.proposalPosts}
-            validators={validators}
-            status={status}
-            gridSize={6}
-          />
-          <Council
-            getMember={getMember}
-            councils={councils}
-            council={status.council}
-            posts={posts}
-            proposals={proposals}
-            stars={stars}
-            status={status}
-            validators={validators}
-            domain={domain}
-            gridSize={6}
-          />
-          <Forum
-            updateForum={props.updateForum}
-            posts={posts}
-            threads={threads}
-            startTime={status.startTime}
-          />
-          <Openings openings={openings} />
-          <Validators
-            toggleStar={toggleStar}
-            councils={councils}
-            members={members}
-            posts={posts}
-            proposals={proposals}
-            nominators={nominators}
-            validators={validators}
-            stashes={stashes}
-            stars={stars}
-            stakes={stakes}
-            rewardPoints={rewardPoints}
-            tokenomics={tokenomics}
-            status={status}
-          />
-        </Grid>
-      </Container>
-    </div>
-  );
+  return <Events selectEvent={props.selectEvent} blocks={props.blocks} />
 };
 
 export default Dashboard;

+ 65 - 0
src/components/Events/index.tsx

@@ -0,0 +1,65 @@
+import { useState } from "react";
+import { Badge, Button } from "react-bootstrap";
+import { IState } from "../../types";
+
+interface IProps extends IState {
+  blocks: { id: number; events: any }[];
+}
+
+const Events = (props: IProps) => {
+  const [hidden,setHidden] = useState(['ExtrinsicSuccess'])
+  const { blocks, selectEvent } = props;
+
+  // all methods and sections
+  const methods = []
+  const sections = []
+  blocks.forEach(block=> block.events?.forEach(event => {
+    if (!methods.includes(event.method)) methods.push(event.method)
+    if (!sections.includes(event.section)) sections.push(event.section)
+  }))
+
+  // filter hidden methods and sections
+  const filteredBlocks = blocks
+    .filter((b) => b.timestamp > 164804000000)
+    .filter(b=> b.events?.filter(e=> !hidden.includes(e.section) && !hidden.includes(e.method)).length)
+    .sort((a, b) => b.id - a.id)
+
+  const toggleHide = (item) => {
+     if (hidden.includes(item)) setHidden(hidden.filter(h=> h !== item))
+     else setHidden(hidden.concat(item))
+  }
+
+  return (
+    <div className="p-3 text-light">
+      <div>
+        <div>Synced {blocks.length} blocks.</div>
+        <b>Sections</b>
+        {sections.map((s,i)=> <Button variant={hidden.includes(s) ? 'outline-dark' : 'dark'} className='btn-sm p-1 m-1 mr-1' key={i} onClick={()=>toggleHide(s)} title={`Click to ${hidden.includes(s) ? `show` : `hide`}`}>{s}</Button>)}
+      </div>
+      <div>
+        <b>Methods</b>
+        {methods.map((m,i)=> <Button variant={hidden.includes(m) ? 'outline-dark' : 'dark'} className='btn-sm p-1 m-1 mr-1' key={i} onClick={()=>toggleHide(m)} title={`Click to ${hidden.includes(m) ? `show` : `hide`}`}>{m}</Button>)}
+      </div>
+      <div className='mt-2'>
+     {filteredBlocks.map((block) => (
+          <div key={block.id} className="d-flex flex-wrap px-2">
+            #{block.id}
+            {block.events?.filter(e=> !hidden.includes(e.section) && !hidden.includes(e.method)).map((event, index: number) => (
+              <Badge
+                key={index}
+                variant="success"
+                className="ml-1"
+                title={JSON.stringify(event.data)}
+                onClick={() => selectEvent(event)}
+              >
+                {event.section}.{event.method}
+              </Badge>
+            ))}
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+};
+
+export default Events;

+ 48 - 0
src/components/Modals/Event.tsx

@@ -0,0 +1,48 @@
+import React from "react";
+import { Button, Modal } from "react-bootstrap";
+
+const Event = (props) => {
+  const {
+    event: { blockId, method, section, data },
+    onHide,
+  } = props;
+  console.debug(`Event`, props);
+  return (
+    <Modal
+      size="lg"
+      aria-labelledby="contained-modal-title-vcenter"
+      centered
+      show={true}
+      onHide={onHide}
+    >
+      <Modal.Header className="no-border" closeButton>
+        <Modal.Title id="contained-modal-title-vcenter ">
+          #{blockId}: {section}.{method}
+        </Modal.Title>
+      </Modal.Header>
+      <Modal.Body className="text-center">
+        {data.map((object, index) => (
+          <ObjectValues key={index} object={object} />
+        ))}
+      </Modal.Body>
+      <Modal.Footer>
+        <Button variant="dark" onClick={() => onHide()}>
+          Close
+        </Button>
+      </Modal.Footer>
+    </Modal>
+  );
+};
+
+const ObjectValues = (props) => {
+  const { object } = props;
+  if (typeof object !== "object") return <div>{object}</div>;
+  return Object.keys(object).map((key) => (
+    <div key={key} className="d-flex flex-row justify-content-between">
+      <div>{key}</div>
+      <ObjectValues object={object[key]} />
+    </div>
+  ));
+};
+
+export default Event;

+ 4 - 1
src/components/Modals/index.tsx

@@ -1,12 +1,15 @@
 import Status from "./Status";
 import EditKpi from "./EditKpi";
+import Event from "./Event";
 
 const Modals = (props) => {
   const { editKpi, toggleEditKpi, showModal, toggleShowStatus, showStatus } =
     props;
   return (
     <div>
-      {editKpi ? (
+      {props.selectedEvent ? (
+        <Event event={props.selectedEvent} onHide={props.selectEvent} />
+      ) : editKpi ? (
         <EditKpi kpi={editKpi} cancel={toggleEditKpi} />
       ) : showStatus ? (
         <Status show={showStatus} onHide={toggleShowStatus} {...props} />

+ 1 - 0
src/components/index.ts

@@ -7,6 +7,7 @@ export { default as Councils } from "./Councils";
 export { default as Council } from "./Dashboard/Council";
 export { default as ElectionStatus } from "./Councils/ElectionStatus";
 export { default as Curation } from "./Curation";
+export { default as Events } from "./Events";
 export { default as Dashboard } from "./Dashboard";
 export { default as Forum } from "./Forum";
 export { default as LatestPost } from "./Forum/LatestPost";

+ 0 - 1
src/lib/queries.ts

@@ -68,7 +68,6 @@ const getGithubDir = async (url: string) => {
 };
 
 export const bootstrap = (save: (key: string, data: any) => {}) => {
-  return;
   [
     { tokenomics: () => getTokenomics() },
     //{ faq: () => getFAQ() },