Browse Source

Fix validator report console errors.

Oleksandr Korniienko 3 năm trước cách đây
mục cha
commit
7159ebf70b
4 tập tin đã thay đổi với 375 bổ sung190 xóa
  1. 1 1
      package.json
  2. 1 1
      src/components/Dashboard/index.tsx
  3. 340 181
      src/components/ValidatorReport/index.tsx
  4. 33 7
      yarn.lock

+ 1 - 1
package.json

@@ -5,7 +5,7 @@
   "repository": "https://github.com/Joystream/community-repo",
   "dependencies": {
     "@material-ui/core": "^4.12.3",
-    "@material-ui/data-grid": "^0.1.67",
+    "@material-ui/data-grid": "^4.0.0-alpha.35",
     "@material-ui/icons": "^4.11.2",
     "@material-ui/lab": "^4.0.0-alpha.60",
     "axios": "^0.21.1",

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

@@ -39,11 +39,11 @@ const Dashboard = (props: IProps) => {
           <Link to={`/timeline`}>Timeline</Link>
           <Link to={`/tokenomics`}>Reports</Link>
           <Link to={`/validators`}>Validators</Link>
+          <Link to={`/validator-report`}>Validator Report</Link>
           <Link to={`/storage`}>Storage</Link>
           <Link to={`/spending`}>Spending</Link>
           <Link to={`/transactions`}>Transfers</Link>
           <Link to={`/burners`}>Top Burners</Link>
-          <Link to={`/validators_report`}>Validators Reporting Tool</Link>
           <Link to="/mint">Toolbox</Link>
         </div>
 

+ 340 - 181
src/components/ValidatorReport/index.tsx

@@ -1,5 +1,5 @@
-import { getChainState } from './get-status'
-import moment from 'moment'
+import { getChainState } from "./get-status";
+import moment from "moment";
 import {
   Card,
   CardActions,
@@ -13,75 +13,85 @@ import {
   TextField,
   Theme,
   Typography,
-} from '@material-ui/core'
-import Button from '@material-ui/core/Button'
-import { BootstrapButton } from './BootstrapButton'
-import Autocomplete, { AutocompleteChangeDetails } from '@material-ui/lab/Autocomplete'
-import { ChangeEvent, FocusEvent, useEffect, useState } from 'react'
-import axios from 'axios'
-import { config } from 'dotenv'
-import { Report, Reports } from './Types'
-import { ColDef, DataGrid, PageChangeParams, ValueFormatterParams } from '@material-ui/data-grid'
-import Alert from '@material-ui/lab/Alert'
-import Tabs from '@material-ui/core/Tabs'
-import Backdrop from '@material-ui/core/Backdrop'
-import { AutocompleteChangeReason } from '@material-ui/lab'
-import './index.css'
+} from "@material-ui/core";
+import Button from "@material-ui/core/Button";
+import { BootstrapButton } from "./BootstrapButton";
+import Autocomplete from "@material-ui/lab/Autocomplete";
+import { ChangeEvent, FocusEvent, useEffect, useState } from "react";
+import axios from "axios";
+import { config } from "dotenv";
+import { Report, Reports } from "./Types";
+import { DataGrid, GridColumns } from "@material-ui/data-grid";
+import Alert from "@material-ui/lab/Alert";
+import Tabs from "@material-ui/core/Tabs";
+import Backdrop from "@material-ui/core/Backdrop";
+import "./index.css";
 
-config()
+config();
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
     root: {
       flexGrow: 1,
-      backgroundColor: '#ffffff',
+      backgroundColor: "#ffffff",
     },
     backdrop: {
       zIndex: theme.zIndex.drawer + 1,
-      color: '#fff',
-      position: 'absolute',
-      width: '100%',
+      color: "#fff",
+      position: "absolute",
+      width: "100%",
     },
-  }),
-)
-
+  })
+);
 
 const ValidatorReport = () => {
-  const dateFormat = 'yyyy-MM-DD'
-  const [activeValidators, setActiveValidators] = useState([])
-  const [lastBlock, setLastBlock] = useState(0)
-  const [stash, setStash] = useState('5EhDdcWm4TdqKp1ew1PqtSpoAELmjbZZLm5E34aFoVYkXdRW')
-  const [dateFrom, setDateFrom] = useState(moment().subtract(14, 'd').format(dateFormat))
-  const [dateTo, setDateTo] = useState(moment().format(dateFormat))
-  const [startBlock, setStartBlock] = useState('' as unknown as number)
-  const [endBlock, setEndBlock] = useState('' as unknown as number)
-  const [isLoading, setIsLoading] = useState(false)
-  const [error, setError] = useState(undefined)
-  const [backendUrl] = useState('https://validators.joystreamstats.live')
-  const [currentPage, setCurrentPage] = useState(1)
-  const [filterTab, setFilterTab] = useState(0 as number)
-  const [columns] = useState(
-    [
-      { field: 'id', headerName: 'Era', width: 150, sortable: true },
-      { field: 'stakeTotal', headerName: 'Total Stake', width: 150, sortable: true },
-      { field: 'stakeOwn', headerName: 'Own Stake', width: 150, sortable: true },
-      { field: 'points', headerName: 'Points', width: 150, sortable: true },
-      { field: 'rewards', headerName: 'Rewards', width: 150, sortable: true },
-      {
-        field: 'commission',
-        headerName: 'Commission',
-        width: 150,
-        sortable: true,
-        valueFormatter: (params: ValueFormatterParams) => {
-          if (isNaN(params.value as unknown as number)) {
-            return `${params.value}%`
-          }
-          return `${Number(params.value).toFixed(0)}%`
-        },
+  const dateFormat = "yyyy-MM-DD";
+  const [activeValidators, setActiveValidators] = useState([]);
+  const [lastBlock, setLastBlock] = useState(0);
+  const [stash, setStash] = useState(
+    "5EhDdcWm4TdqKp1ew1PqtSpoAELmjbZZLm5E34aFoVYkXdRW"
+  );
+  const [dateFrom, setDateFrom] = useState(
+    moment().subtract(14, "d").format(dateFormat)
+  );
+  const [dateTo, setDateTo] = useState(moment().format(dateFormat));
+  const [startBlock, setStartBlock] = useState("" as unknown as number);
+  const [endBlock, setEndBlock] = useState("" as unknown as number);
+  const [isLoading, setIsLoading] = useState(false);
+  const [error, setError] = useState(undefined);
+  const [backendUrl] = useState("https://validators.joystreamstats.live");
+  const [currentPage, setCurrentPage] = useState(1);
+  const [filterTab, setFilterTab] = useState(0 as number);
+  const [columns] = useState([
+    { field: "id", headerName: "Era", width: 150, sortable: true },
+    {
+      field: "stakeTotal",
+      headerName: "Total Stake",
+      width: 150,
+      sortable: true,
+    },
+    { field: "stakeOwn", headerName: "Own Stake", width: 150, sortable: true },
+    { field: "points", headerName: "Points", width: 150, sortable: true },
+    { field: "rewards", headerName: "Rewards", width: 150, sortable: true },
+    {
+      field: "commission",
+      headerName: "Commission",
+      width: 150,
+      sortable: true,
+      valueFormatter: (params: { value: number }) => {
+        if (isNaN(params.value)) {
+          return `${params.value}%`;
+        }
+        return `${Number(params.value).toFixed(0)}%`;
       },
-      { field: 'blocksCount', headerName: 'Blocks Produced', width: 150, sortable: true },
-    ],
-  )
+    },
+    {
+      field: "blocksCount",
+      headerName: "Blocks Produced",
+      width: 150,
+      sortable: true,
+    },
+  ]);
   const [report, setReport] = useState({
     pageSize: 0,
     totalCount: 0,
@@ -93,156 +103,281 @@ const ValidatorReport = () => {
     startTime: -1,
     endTime: -1,
     report: [] as unknown as Report[],
-  } as unknown as Reports)
+  } as unknown as Reports);
 
-  const isDateRange = filterTab === 0
-  const isBlockRange = filterTab === 1
+  const isDateRange = filterTab === 0;
+  const isBlockRange = filterTab === 1;
 
   useEffect(() => {
-    updateChainState()
+    updateChainState();
     const interval = setInterval(() => {
-      updateChainState()
-    }, 10000)
-    return () => clearInterval(interval)
-  }, [])
+      updateChainState();
+    }, 10000);
+    return () => clearInterval(interval);
+  }, []);
 
   const updateChainState = () => {
     getChainState().then((chainState) => {
-      setLastBlock(chainState.finalizedBlockHeight)
-      setActiveValidators(chainState.validators.validators)
-    })
-  }
+      setLastBlock(chainState.finalizedBlockHeight);
+      setActiveValidators(chainState.validators.validators);
+    });
+  };
 
-  const handlePageChange = (params: PageChangeParams) => {
+  const handlePageChange = (page: number) => {
     if (report.totalCount > 0) {
-      loadReport(params.page)
+      loadReport(page);
     }
-  }
+  };
 
   const loadReport = (page: number) => {
-    setCurrentPage(page)
-    setIsLoading(true)
-    const blockParam = isBlockRange && startBlock && endBlock ? `&start_block=${startBlock}&end_block=${endBlock}` : ''
-    const dateParam = isDateRange && dateFrom && dateTo ? `&start_time=${moment(dateFrom, dateFormat).format(dateFormat)}&end_time=${moment(dateTo, dateFormat).format(dateFormat)}` : ''
-    const apiUrl = `${backendUrl}/validator-report?addr=${stash}&page=${page}${blockParam}${dateParam}`
-    axios.get(apiUrl).then((response) => {
-      if (response.data.report !== undefined) {
-        setReport(response.data)
-      }
-      setIsLoading(false)
-      setError(undefined)
-    }).catch((err) => {
-      setIsLoading(false)
-      setError(err)
-    })
-  }
+    setCurrentPage(page);
+    setIsLoading(true);
+    const blockParam =
+      isBlockRange && startBlock && endBlock
+        ? `&start_block=${startBlock}&end_block=${endBlock}`
+        : "";
+    const dateParam =
+      isDateRange && dateFrom && dateTo
+        ? `&start_time=${moment(dateFrom, dateFormat).format(
+            dateFormat
+          )}&end_time=${moment(dateTo, dateFormat).format(dateFormat)}`
+        : "";
+    const apiUrl = `${backendUrl}/validator-report?addr=${stash}&page=${page}${blockParam}${dateParam}`;
+    axios
+      .get(apiUrl)
+      .then((response) => {
+        if (response.data.report !== undefined) {
+          setReport(response.data);
+        }
+        setIsLoading(false);
+        setError(undefined);
+      })
+      .catch((err) => {
+        setIsLoading(false);
+        setError(err);
+      });
+  };
 
   const stopLoadingReport = () => {
-    setIsLoading(false)
-  }
+    setIsLoading(false);
+  };
 
-  const canLoadReport = () => stash && ((isBlockRange && startBlock && endBlock) || (isDateRange && dateFrom && dateTo))
-  const startOrStopLoading = () => isLoading ? stopLoadingReport() : loadReport(1)
-  const updateStartBlock = (e: { target: { value: unknown; }; }) => setStartBlock((e.target.value as unknown as number))
-  const updateEndBlock = (e: { target: { value: unknown; }; }) => setEndBlock((e.target.value as unknown as number))
-  const updateDateFrom = (e: { target: { value: unknown; }; }) => setDateFrom((e.target.value as unknown as string))
-  const updateDateTo = (e: { target: { value: unknown; }; }) => setDateTo((e.target.value as unknown as string))
+  const canLoadReport = () =>
+    stash &&
+    ((isBlockRange && startBlock && endBlock) ||
+      (isDateRange && dateFrom && dateTo));
+  const startOrStopLoading = () =>
+    isLoading ? stopLoadingReport() : loadReport(1);
+  const updateStartBlock = (e: { target: { value: unknown } }) =>
+    setStartBlock(e.target.value as unknown as number);
+  const updateEndBlock = (e: { target: { value: unknown } }) =>
+    setEndBlock(e.target.value as unknown as number);
+  const updateDateFrom = (e: { target: { value: unknown } }) =>
+    setDateFrom(e.target.value as unknown as string);
+  const updateDateTo = (e: { target: { value: unknown } }) =>
+    setDateTo(e.target.value as unknown as string);
 
   const setCurrentPeriodStartBlock = () => {
-    const blocksToEndOfDay = moment().endOf('d').diff(moment(), 'seconds') / 6
-    const twoWeeksBlocks = (600 * 24 * 14)
-    return setStartBlock(lastBlock - twoWeeksBlocks - Number(blocksToEndOfDay.toFixed(0)))
-  }
+    const blocksToEndOfDay = moment().endOf("d").diff(moment(), "seconds") / 6;
+    const twoWeeksBlocks = 600 * 24 * 14;
+    return setStartBlock(
+      lastBlock - twoWeeksBlocks - Number(blocksToEndOfDay.toFixed(0))
+    );
+  };
 
-  const setCurrentPeriodEndBlock = () => setEndBlock(lastBlock)
+  const setCurrentPeriodEndBlock = () => setEndBlock(lastBlock);
 
   const getButtonTitle = (isLoading: boolean) => {
     if (isLoading) {
-      return (<div style={{ display: 'flex', alignItems: 'center' }}>Stop loading <CircularProgress
-        style={{ color: '#fff', height: 20, width: 20, marginLeft: 12 }} /></div>)
+      return (
+        <div style={{ display: "flex", alignItems: "center" }}>
+          Stop loading{" "}
+          <CircularProgress
+            style={{ color: "#fff", height: 20, width: 20, marginLeft: 12 }}
+          />
+        </div>
+      );
     }
     if (isBlockRange) {
-      return startBlock && endBlock ? `Load data between blocks ${startBlock} - ${endBlock}` : 'Load data between blocks'
+      return startBlock && endBlock
+        ? `Load data between blocks ${startBlock} - ${endBlock}`
+        : "Load data between blocks";
     }
     if (isDateRange) {
-      return dateFrom && dateTo ? `Load data between dates ${dateFrom} - ${dateTo}` : 'Load data between dates'
+      return dateFrom && dateTo
+        ? `Load data between dates ${dateFrom} - ${dateTo}`
+        : "Load data between dates";
     }
-    return 'Choose dates or blocks range'
-  }
-  const updateStash = (event: ChangeEvent<{}>, value: string | null, reason: AutocompleteChangeReason, details?: AutocompleteChangeDetails<string> | undefined) => {
-    setStash(value || '')
-  }
+    return "Choose dates or blocks range";
+  };
+  const updateStash = (
+    event: ChangeEvent<{}>,
+    value: string | null
+  ) => {
+    setStash(value || "");
+  };
 
-  const updateStashOnBlur = (event: FocusEvent<HTMLDivElement> & { target: HTMLInputElement }) => {
-    setStash((prev) => prev !== event.target.value ? event.target.value : prev)
-  }
+  const updateStashOnBlur = (
+    event: FocusEvent<HTMLDivElement> & { target: HTMLInputElement }
+  ) => {
+    setStash((prev) =>
+      prev !== event.target.value ? event.target.value : prev
+    );
+  };
 
-  const classes = useStyles()
+  const classes = useStyles();
   return (
     <div className={classes.root}>
-      <Container maxWidth='lg'>
+      <Container maxWidth="lg">
         <Grid container spacing={2}>
           <Grid item lg={12}>
-            <div style={{ display: 'flex', justifyContent: 'flex-start' }}>
+            <div style={{ display: "flex", justifyContent: "flex-start" }}>
               <h1>Validator Report</h1>
             </div>
           </Grid>
           <Grid item xs={12} lg={12}>
             <Autocomplete
               freeSolo
-              style={{ width: '100%' }}
+              style={{ width: "100%" }}
               options={activeValidators}
               onChange={updateStash}
               onBlur={updateStashOnBlur}
               value={stash}
-              renderInput={(params) => <TextField {...params} label='Validator stash address' variant='filled' />} />
+              renderInput={(params) => (
+                <TextField
+                  {...params}
+                  label="Validator stash address"
+                  variant="filled"
+                />
+              )}
+            />
           </Grid>
           <Grid item xs={12} lg={12}>
-            <Tabs indicatorColor='primary' value={filterTab}
-                  onChange={(e: unknown, newValue: number) => setFilterTab(newValue)} aria-label='simple tabs example'>
-              <Tab label='Search by date' />
-              <Tab label='Search by blocks' />
+            <Tabs
+              indicatorColor="primary"
+              value={filterTab}
+              onChange={(e: unknown, newValue: number) =>
+                setFilterTab(newValue)
+              }
+              aria-label="simple tabs example"
+            >
+              <Tab label="Search by date" />
+              <Tab label="Search by blocks" />
             </Tabs>
           </Grid>
           <Grid hidden={!isDateRange} item xs={6} lg={3}>
-            <TextField fullWidth type='date' onChange={updateDateFrom} id='block-start'
-                       InputLabelProps={{ shrink: true }} label='Date From' value={dateFrom} variant='filled' />
+            <TextField
+              fullWidth
+              type="date"
+              onChange={updateDateFrom}
+              id="block-start"
+              InputLabelProps={{ shrink: true }}
+              label="Date From"
+              value={dateFrom}
+              variant="filled"
+            />
           </Grid>
           <Grid hidden={!isDateRange} item xs={6} lg={3}>
-            <BootstrapButton size='large' style={{ height: 56 }} fullWidth
-                             onClick={() => setDateFrom(moment().subtract(2, 'w').format('yyyy-MM-DD'))}>2 weeks from
-              today</BootstrapButton>
+            <BootstrapButton
+              size="large"
+              style={{ height: 56 }}
+              fullWidth
+              onClick={() =>
+                setDateFrom(moment().subtract(2, "w").format("yyyy-MM-DD"))
+              }
+            >
+              2 weeks from today
+            </BootstrapButton>
           </Grid>
           <Grid hidden={!isDateRange} item xs={6} lg={3}>
-            <TextField fullWidth type='date' onChange={updateDateTo} id='block-end' InputLabelProps={{ shrink: true }}
-                       label='Date To' value={dateTo} variant='filled' />
+            <TextField
+              fullWidth
+              type="date"
+              onChange={updateDateTo}
+              id="block-end"
+              InputLabelProps={{ shrink: true }}
+              label="Date To"
+              value={dateTo}
+              variant="filled"
+            />
           </Grid>
           <Grid hidden={!isDateRange} item xs={6} lg={3}>
-            <BootstrapButton size='large' style={{ height: 56 }} fullWidth
-                             onClick={() => setDateTo(moment().format('yyyy-MM-DD'))}>Today</BootstrapButton>
+            <BootstrapButton
+              size="large"
+              style={{ height: 56 }}
+              fullWidth
+              onClick={() => setDateTo(moment().format("yyyy-MM-DD"))}
+            >
+              Today
+            </BootstrapButton>
           </Grid>
           <Grid hidden={!isBlockRange} item xs={6} lg={3}>
-            <TextField fullWidth type='number' onChange={updateStartBlock} id='block-start' label='Start Block'
-                       value={startBlock} variant='filled' />
+            <TextField
+              fullWidth
+              type="number"
+              onChange={updateStartBlock}
+              id="block-start"
+              label="Start Block"
+              value={startBlock}
+              variant="filled"
+            />
           </Grid>
           <Grid hidden={!isBlockRange} item xs={6} lg={3}>
-            <BootstrapButton size='large' style={{ height: 56 }} fullWidth disabled={!lastBlock}
-                             onClick={setCurrentPeriodStartBlock}>{lastBlock ? `2 weeks before latest (${lastBlock - (600 * 24 * 14)})` : '2 weeks from latest'}</BootstrapButton>
+            <BootstrapButton
+              size="large"
+              style={{ height: 56 }}
+              fullWidth
+              disabled={!lastBlock}
+              onClick={setCurrentPeriodStartBlock}
+            >
+              {lastBlock
+                ? `2 weeks before latest (${lastBlock - 600 * 24 * 14})`
+                : "2 weeks from latest"}
+            </BootstrapButton>
           </Grid>
           <Grid hidden={!isBlockRange} item xs={6} lg={3}>
-            <TextField fullWidth type='number' onChange={updateEndBlock} id='block-end' label='End Block'
-                       value={endBlock} variant='filled' />
+            <TextField
+              fullWidth
+              type="number"
+              onChange={updateEndBlock}
+              id="block-end"
+              label="End Block"
+              value={endBlock}
+              variant="filled"
+            />
           </Grid>
           <Grid hidden={!isBlockRange} item xs={6} lg={3}>
-            <BootstrapButton size='large' style={{ height: 56 }} fullWidth disabled={!lastBlock}
-                             onClick={setCurrentPeriodEndBlock}>{lastBlock ? `Pick latest block (${lastBlock})` : 'Use latest block'}</BootstrapButton>
+            <BootstrapButton
+              size="large"
+              style={{ height: 56 }}
+              fullWidth
+              disabled={!lastBlock}
+              onClick={setCurrentPeriodEndBlock}
+            >
+              {lastBlock
+                ? `Pick latest block (${lastBlock})`
+                : "Use latest block"}
+            </BootstrapButton>
           </Grid>
           <Grid item xs={12} lg={12}>
-            <BootstrapButton size='large' style={{ height: 56 }} fullWidth disabled={!canLoadReport()}
-                             onClick={startOrStopLoading}>{getButtonTitle(isLoading)}</BootstrapButton>
-            <Alert style={error !== undefined ? { marginTop: 12 } : { display: 'none' }}
-                   onClose={() => setError(undefined)} severity='error'>Error loading validator report, please try
-              again.</Alert>
+            <BootstrapButton
+              size="large"
+              style={{ height: 56 }}
+              fullWidth
+              disabled={!canLoadReport()}
+              onClick={startOrStopLoading}
+            >
+              {getButtonTitle(isLoading)}
+            </BootstrapButton>
+            <Alert
+              style={
+                error !== undefined ? { marginTop: 12 } : { display: "none" }
+              }
+              onClose={() => setError(undefined)}
+              severity="error"
+            >
+              Error loading validator report, please try again.
+            </Alert>
           </Grid>
           <Grid item xs={12} lg={12}>
             <ValidatorReportCard stash={stash} report={report} />
@@ -250,17 +385,17 @@ const ValidatorReport = () => {
           <Grid item xs={12} lg={12}>
             <div style={{ height: 400 }}>
               <Backdrop className={classes.backdrop} open={isLoading}>
-                <CircularProgress color='inherit' />
+                <CircularProgress color="inherit" />
               </Backdrop>
               <DataGrid
                 rows={report.report}
-                columns={columns as unknown as ColDef[]}
+                columns={columns as unknown as GridColumns}
                 rowCount={report.totalCount}
                 pagination
-                paginationMode='server'
+                paginationMode="server"
                 onPageChange={handlePageChange}
                 pageSize={report.pageSize}
-                rowsPerPageOptions={[]}
+                rowsPerPageOptions={[0]}
                 disableSelectionOnClick
                 page={currentPage}
               />
@@ -269,16 +404,17 @@ const ValidatorReport = () => {
         </Grid>
       </Container>
     </div>
-  )
-}
+  );
+};
 
-const ValidatorReportCard = (props: { stash: string, report: Reports }) => {
-  const copyValidatorStatistics = () => navigator.clipboard.writeText(scoringPeriodText)
-  const [scoringPeriodText, setScoringPeriodText] = useState('')
+const ValidatorReportCard = (props: { stash: string; report: Reports }) => {
+  const copyValidatorStatistics = () =>
+    navigator.clipboard.writeText(scoringPeriodText);
+  const [scoringPeriodText, setScoringPeriodText] = useState("");
   const useStyles = makeStyles({
     root: {
-      minWidth: '100%',
-      textAlign: 'left',
+      minWidth: "100%",
+      textAlign: "left",
     },
     title: {
       fontSize: 18,
@@ -286,47 +422,70 @@ const ValidatorReportCard = (props: { stash: string, report: Reports }) => {
     pos: {
       marginTop: 12,
     },
-  })
+  });
 
-  const classes = useStyles()
+  const classes = useStyles();
 
   useEffect(() => {
-    updateScoringPeriodText()
-  })
+    updateScoringPeriodText();
+  });
 
   const updateScoringPeriodText = () => {
     if (props.report.report.length > 0) {
-      const scoringDateFormat = 'DD-MM-yyyy'
-      const report = `Validator Date: ${moment(props.report.startTime).format(scoringDateFormat)} - ${moment(props.report.endTime).format(scoringDateFormat)}\nDescription: I was an active validator from era/block ${props.report.startEra}/${props.report.startBlock} to era/block ${props.report.endEra}/${props.report.endBlock}\nwith stash account ${props.stash}. (I was active in all the eras in this range and found a total of ${props.report.totalBlocks} blocks)`
-      setScoringPeriodText(report)
+      const scoringDateFormat = "DD-MM-yyyy";
+      const report = `Validator Date: ${moment(props.report.startTime).format(
+        scoringDateFormat
+      )} - ${moment(props.report.endTime).format(
+        scoringDateFormat
+      )}\nDescription: I was an active validator from era/block ${
+        props.report.startEra
+      }/${props.report.startBlock} to era/block ${props.report.endEra}/${
+        props.report.endBlock
+      }\nwith stash account ${
+        props.stash
+      }. (I was active in all the eras in this range and found a total of ${
+        props.report.totalBlocks
+      } blocks)`;
+      setScoringPeriodText(report);
     } else {
-      setScoringPeriodText('')
+      setScoringPeriodText("");
     }
-  }
+  };
 
   if (props.report.report.length > 0) {
-    return (<Card className={classes.root}>
-      <CardContent>
-        <Typography className={classes.title} color='textPrimary' gutterBottom>
-          Validator Report:
-        </Typography>
-        {scoringPeriodText.split('\n').map((i, key) => <Typography key={key} className={classes.pos}
-                                                                   color='textSecondary'>{i}</Typography>)}
-      </CardContent>
-      <CardActions>
-        <Button onClick={copyValidatorStatistics} size='small'>Copy to clipboard</Button>
-      </CardActions>
-    </Card>)
+    return (
+      <Card className={classes.root}>
+        <CardContent>
+          <Typography
+            className={classes.title}
+            color="textPrimary"
+            gutterBottom
+          >
+            Validator Report:
+          </Typography>
+          {scoringPeriodText.split("\n").map((i, key) => (
+            <Typography key={key} className={classes.pos} color="textSecondary">
+              {i}
+            </Typography>
+          ))}
+        </CardContent>
+        <CardActions>
+          <Button onClick={copyValidatorStatistics} size="small">
+            Copy to clipboard
+          </Button>
+        </CardActions>
+      </Card>
+    );
   }
   return (
     <Card className={classes.root}>
       <CardContent>
-        <Typography className={classes.pos} color='textSecondary'>
+        <Typography className={classes.pos} color="textSecondary">
           No Data Available
         </Typography>
       </CardContent>
     </Card>
-  )
-}
+  );
+};
 
-export default ValidatorReport
+export default ValidatorReport;

+ 33 - 7
yarn.lock

@@ -1531,12 +1531,15 @@
     react-is "^16.8.0 || ^17.0.0"
     react-transition-group "^4.4.0"
 
-"@material-ui/data-grid@^0.1.67":
-  version "0.1.67"
-  resolved "https://registry.yarnpkg.com/@material-ui/data-grid/-/data-grid-0.1.67.tgz#98eb6df011821cefe19bfd00dec353bb316cdf8c"
-  integrity sha512-YxI7P98MKe/XEiX/Gnabc1WFBQIkK9yxlWV0nSaQgr3xaDGcEhTnXsMCjkeBtnkVy33Mu4iTSrQ/L/FcgwpmKA==
+"@material-ui/data-grid@^4.0.0-alpha.35":
+  version "4.0.0-alpha.35"
+  resolved "https://registry.yarnpkg.com/@material-ui/data-grid/-/data-grid-4.0.0-alpha.35.tgz#be49d2c15571b46cd570e2172cb6737c6c479de2"
+  integrity sha512-liyubO6MszF61Ceqx/yMGM0iWPVjg2+yf63m95qUSAp/Q1DyJenWP0XD78YZ1ZCqWbH1LIxJiuHEO+9iqKa7wA==
   dependencies:
-    tslib "^2.0.0"
+    "@material-ui/utils" "^5.0.0-alpha.14"
+    clsx "^1.0.4"
+    prop-types "^15.7.2"
+    reselect "^4.0.0"
 
 "@material-ui/icons@^4.11.2":
   version "4.11.2"
@@ -1602,6 +1605,17 @@
     prop-types "^15.7.2"
     react-is "^16.8.0 || ^17.0.0"
 
+"@material-ui/utils@^5.0.0-alpha.14":
+  version "5.0.0-beta.1"
+  resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-5.0.0-beta.1.tgz#ed8fa3f9143feb051b894215b6cf3d69c71fe571"
+  integrity sha512-63E5b1iW79T6dga7Ao1turX4s5P8jipCMVw1tDjKHMiauILb8C6TmUPde+NoM+fQ6OTppC9JxdOXzuotxNRWNA==
+  dependencies:
+    "@babel/runtime" "^7.4.4"
+    "@types/prop-types" "^15.7.3"
+    "@types/react-is" "^16.7.1 || ^17.0.0"
+    prop-types "^15.7.2"
+    react-is "^17.0.0"
+
 "@nodelib/fs.scandir@2.1.5":
   version "2.1.5"
   resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -2264,6 +2278,13 @@
   dependencies:
     "@types/react" "^16"
 
+"@types/react-is@^16.7.1 || ^17.0.0":
+  version "17.0.2"
+  resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-17.0.2.tgz#abc4d910bff5b0bc6b3e1bec57575f6b63fd4e05"
+  integrity sha512-2+L0ilcAEG8udkDnvx8B0upwXFBbNnVwOsSCTxW3SDOkmar9NyEeLG0ZLa3uOEw9zyYf/fQapcnfXAVmDKlyHw==
+  dependencies:
+    "@types/react" "*"
+
 "@types/react-router-dom@^5.1.6":
   version "5.1.8"
   resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.8.tgz#bf3e1c8149b3d62eaa206d58599de82df0241192"
@@ -10545,7 +10566,7 @@ react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
-"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.1:
+"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.0, react-is@^17.0.1:
   version "17.0.2"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
   integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
@@ -10977,6 +10998,11 @@ requires-port@^1.0.0:
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
   integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
 
+reselect@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
+  integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
+
 resolve-cwd@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -12291,7 +12317,7 @@ tslib@^1.8.1, tslib@^1.9.0:
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
 
-tslib@^2.0.0, tslib@^2.0.3:
+tslib@^2.0.3:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
   integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==