Joystream Stats 2 роки тому
батько
коміт
6f7030ad2c
4 змінених файлів з 65 додано та 23 видалено
  1. 1 1
      package.json
  2. 53 11
      src/App.tsx
  3. 1 1
      src/components/Members/index.tsx
  4. 10 10
      src/lib/queries.ts

+ 1 - 1
package.json

@@ -59,7 +59,7 @@
     ]
   },
   "devDependencies": {
-    "typescript": "^4.4.3",
+    "typescript": "^4.4.0",
     "@types/bootstrap": "^5.0.1",
     "@types/jest": "^26.0.15",
     "@types/node": "^12.0.0",

+ 53 - 11
src/App.tsx

@@ -34,6 +34,9 @@ import { Header } from "@polkadot/types/interfaces";
 interface IProps {}
 
 class App extends React.Component<IProps, IState> {
+  private api = this.connectApi(); // joystream API endpoint
+  //private socket = this.initializeSocket(); // jsstats socket.io
+
   initializeSocket() {
     socket.on("disconnect", () => setTimeout(this.initializeSocket, 1000));
     socket.on("connect", () => {
@@ -68,6 +71,7 @@ class App extends React.Component<IProps, IState> {
 
     const nextMemberId = await await api.query.members.nextMemberId();
     status.members = nextMemberId - 1;
+    this.fetchMembers(api, status.members);
     status.proposals = await get.proposalCount(api);
     status.posts = await get.currentPostId(api);
     status.threads = await get.currentThreadId(api);
@@ -82,6 +86,33 @@ class App extends React.Component<IProps, IState> {
     return status;
   }
 
+  fetchMembers(api: ApiPromise, max: number) {
+    // fallback for failing cache
+    let missing = [];
+    for (let id = max; id > 0; --id) {
+      if (!this.state.members.find((m) => m.id === id)) missing.push(id);
+    }
+    if (missing.length < 100)
+      missing.forEach((id) => this.fetchMember(api, id));
+    else
+      api.query.members.membershipById.entries().then((map) => {
+        let members = [];
+        for (const [storageKey, member] of map) {
+          members.push({ ...member.toJSON(), id: storageKey.args[1] });
+        }
+        this.save("members", members);
+        console.debug(`got members`, members);
+      });
+  }
+
+  fetchMember(api: ApiPromise, id: number) {
+    get.membership(api, id).then((member) => {
+      console.debug(`got member ${id} ${member.handle}`);
+      const members = this.state.members.filter((m) => m.id !== id);
+      this.save("members", members.concat({ ...member, id }));
+    });
+  }
+
   async getElectionStatus(api: ApiPromise): Promise<IElectionState> {
     getCouncilSize(api).then((councilSize) => {
       let election = this.state.election;
@@ -180,6 +211,7 @@ class App extends React.Component<IProps, IState> {
 
   async updateCouncils() {
     queryJstats(`v1/councils`).then((councils) => {
+      if (!councils) return;
       this.save(`councils`, councils);
 
       // TODO OPTIMIZE find max round
@@ -211,11 +243,21 @@ class App extends React.Component<IProps, IState> {
     this.setState({ hideFooter: !this.state.hideFooter });
   }
 
-  getMember(handle: string) {
+  getMember(input: string) {
     const { members } = this.state;
-    const member = members.find((m) => m.handle === handle);
+    let member;
+    // search by handle
+    member = members.find((m) => m.handle === input);
     if (member) return member;
-    return members.find((m) => m.rootKey === handle);
+
+    // search by key
+    member = members.find((m) => m.rootKey === input);
+    if (member) return member;
+
+    // TODO fetch live
+    //member = await get.membership(this.api, input)
+    //member = await get.memberIdByAccount(this.api, input)
+    return {};
   }
 
   render() {
@@ -251,10 +293,10 @@ class App extends React.Component<IProps, IState> {
 
   // startup from bottom up
 
-  joyApi() {
+  connectApi() {
     console.debug(`Connecting to ${wsLocation}`);
     const provider = new WsProvider(wsLocation);
-    ApiPromise.create({ provider, types }).then(async (api) => {
+    return ApiPromise.create({ provider, types }).then(async (api) => {
       await api.isReady;
       console.log(`Connected to ${wsLocation}`);
       this.setState({ connected: true });
@@ -277,6 +319,7 @@ class App extends React.Component<IProps, IState> {
         blocks = blocks.filter((i) => i.id !== id).concat(status.block);
         this.setState({ blocks });
       });
+      return api;
     });
   }
 
@@ -295,12 +338,13 @@ class App extends React.Component<IProps, IState> {
   load(key: string) {
     try {
       const data = localStorage.getItem(key);
-      if (!data) return;
+      if (!data) return console.debug(`loaded empty`, key);
       const size = data.length;
       if (size > 10240)
         console.debug(` -${key}: ${(size / 1024).toFixed(1)} KB`);
-      this.setState({ [key]: JSON.parse(data) });
-      return JSON.parse(data);
+      const value = JSON.parse(data) || [];
+      this.setState({ [key]: value });
+      return value;
     } catch (e) {
       console.warn(`Failed to load ${key}`, e);
     }
@@ -308,7 +352,7 @@ 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 mints openings tokenomics transactions reports validators nominators staches stakes rewardPoints stars"
+    "status members assets providers councils council election workers categories channels proposals posts threads mints openings tokenomics transactions reports validators nominators stashes stakes rewardPoints stars"
       .split(" ")
       .map((key) => this.load(key));
     getTokenomics().then((tokenomics) => this.save(`tokenomics`, tokenomics));
@@ -318,8 +362,6 @@ class App extends React.Component<IProps, IState> {
 
   componentDidMount() {
     this.loadData(); // local storage + bootstrap
-    this.joyApi(); // joystream rpc connection
-    //this.initializeSocket() // jsstats socket.io
   }
 
   constructor(props: IProps) {

+ 1 - 1
src/components/Members/index.tsx

@@ -51,7 +51,7 @@ class Members extends React.Component<IProps, IState> {
           {cols.map((col, index: number) => (
             <div key={`col-${index}`} className="d-flex flex-column col-3 p-0">
               {col.map((m) => (
-                <div key={String(m.account)} className="box">
+                <div key={`member-${m.id}`} className="box">
                   <MemberBox
                     id={Number(m.id)}
                     account={m.rootKey}

+ 10 - 10
src/lib/queries.ts

@@ -4,10 +4,13 @@ import axios from "axios";
 
 export const queryJstats = (route: string) => {
   const url = `${apiLocation}/${route}`;
-  return axios.get(url).then(({ data }) => {
-    if (data && !data.error) return data;
-    return console.error(`Jstats query failed: ${route}`, data);
-  });
+  return axios
+    .get(url)
+    .then(({ data }) => {
+      if (data && !data.error) return data;
+      return console.error(`Jstats query failed: ${route}`, data);
+    })
+    .catch((e: any) => console.warn(`Failed to fetch ${route}: ${e.message}`));
 };
 
 export const getTokenomics = async (old?: Tokenomics) => {
@@ -37,10 +40,7 @@ export const getReports = async () => {
     archive: `${apiBase}/archived-reports`,
     template: `${domain}/templates/council_report_template_v1.md`,
   };
-
   ["alexandria", "archive"].map((folder) => getGithubDir(urls[folder]));
-
-  // template
   getGithubFile(urls.template);
 };
 
@@ -51,7 +51,6 @@ const getGithubFile = async (url: string): Promise<string> => {
 
 const getGithubDir = async (url: string) => {
   const { data } = await axios.get(url);
-
   data.forEach(
     async (o: {
       name: string;
@@ -83,8 +82,9 @@ export const bootstrap = (save: (key: string, data: any) => {}) => {
   ].reduce(async (promise, request) => {
     //promise.then(async () => {
     const key = Object.keys(request)[0];
-    console.debug(`Requesting ${key}`);
-    return save(key, await request[key]());
+    //console.debug(`Requesting ${key}`);
+    const result = await request[key]();
+    return result ? save(key, result) : [];
     //}, new Promise((res) => res))
   });
 };