@material-ui/core#TableContainer TypeScript Examples

The following examples show how to use @material-ui/core#TableContainer. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ModRowSection.tsx    From ow-mod-manager with MIT License 6 votes vote down vote up
ModRowSection: React.FunctionComponent<Props> = ({
  mods,
  title,
  highlighted,
}) => {
  const styles = useStyles();

  return mods.length > 0 ? (
    <div className={styles.wrapper}>
      <TableContainer
        component={Paper}
        className={highlighted ? styles.required : ''}
      >
        <Table className={styles.modsTable} size="small">
          <ModTableHead title={title} />
          <TableBody>
            {mods.map((mod: Mod) => (
              <ModTableRow mod={mod} key={mod.uniqueName} />
            ))}
          </TableBody>
        </Table>
      </TableContainer>
    </div>
  ) : (
    <></>
  );
}
Example #2
Source File: OpenOrdersDialog.tsx    From swap-ui with Apache License 2.0 6 votes vote down vote up
function OpenOrdersAccounts() {
  const styles = useStyles();
  const openOrders = useOpenOrders();
  const openOrdersEntries: Array<[PublicKey, OpenOrders[]]> = useMemo(() => {
    return Array.from(openOrders.entries()).map(([market, oo]) => [
      new PublicKey(market),
      oo,
    ]);
  }, [openOrders]);
  return (
    <TableContainer component={Paper} elevation={0}>
      <Table className={styles.table} aria-label="simple table">
        <TableHead>
          <TableRow>
            <TableCell>Market</TableCell>
            <TableCell align="center">Open Orders Account</TableCell>
            <TableCell align="center">Base Used</TableCell>
            <TableCell align="center">Base Free</TableCell>
            <TableCell align="center">Quote Used</TableCell>
            <TableCell align="center">Quote Free</TableCell>
            <TableCell align="center">Settle</TableCell>
            <TableCell align="center">Close</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {openOrdersEntries.map(([market, oos]) => {
            return (
              <OpenOrdersRow
                key={market.toString()}
                market={market}
                openOrders={oos}
              />
            );
          })}
        </TableBody>
      </Table>
    </TableContainer>
  );
}
Example #3
Source File: TableDetails.tsx    From SeeQR with MIT License 6 votes vote down vote up
TableDetails = ({ table }: TableDetailsProps) => (
  <>
    <Typography variant="h3">{`${table?.table_name}`}</Typography>
    <br />
    <TableContainer component={StyledPaper}>
      <Table>
        <TableHead>
          <TableRow>
            <TableCell>
              <strong>Column</strong>
            </TableCell>
            <TableCell align="right">
              <strong>Type</strong>
            </TableCell>
            <TableCell align="right">
              <strong>Is Nullable?</strong>
            </TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {table?.columns.map((row) => (
            <TableRow key={row.column_name}>
              <StyledCell key={row?.column_name}>{row?.column_name}</StyledCell>
              <StyledCell align="right">
                {`${row?.data_type}${
                  row?.character_maximum_length
                    ? `(${row.character_maximum_length})`
                    : ''
                }`}
              </StyledCell>
              <StyledCell align="right">{row?.is_nullable}</StyledCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  </>
)
Example #4
Source File: PlanDetails.tsx    From SeeQR with MIT License 6 votes vote down vote up
PlanDetails = ({ plan, open, handleClose }: PlanDetailsProps) => (
  <StyledModal
    aria-labelledby="transition-modal-title"
    aria-describedby="transition-modal-description"
    open={open}
    onClose={handleClose}
    closeAfterTransition
    BackdropComponent={Backdrop}
    BackdropProps={{
      timeout: 500,
    }}
  >
    <Fade in={open}>
      <TableContainer component={LightPaper}>
        <Table size="small">
          <TableBody>{detailRows(plan)}</TableBody>
        </Table>
      </TableContainer>
    </Fade>
  </StyledModal>
)
Example #5
Source File: BuildWithStepsPage.tsx    From backstage with Apache License 2.0 5 votes vote down vote up
BuildWithStepsView = () => {
  // TODO: Add a test that react-router decodes this (even though `generatePath` doesn't encode it for you!)
  const { jobFullName, buildNumber } = useRouteRefParams(buildRouteRef);
  const classes = useStyles();

  const [{ value }] = useBuildWithSteps({ jobFullName, buildNumber });

  return (
    <div className={classes.root}>
      <Breadcrumbs aria-label="breadcrumb">
        {/* TODO: don't hardcode this link */}
        <Link to="../../..">Projects</Link>
        <Typography>Run</Typography>
      </Breadcrumbs>
      <Box m={2} />
      <TableContainer component={Paper} className={classes.table}>
        <Table>
          <TableBody>
            <TableRow>
              <TableCell>
                <Typography noWrap>Branch</Typography>
              </TableCell>
              <TableCell>{value?.source?.branchName}</TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography noWrap>Message</Typography>
              </TableCell>
              <TableCell>{value?.source?.displayName}</TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography noWrap>Commit ID</Typography>
              </TableCell>
              <TableCell>{value?.source?.commit?.hash}</TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography noWrap>Status</Typography>
              </TableCell>
              <TableCell>
                <JenkinsRunStatus status={value?.status} />
              </TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography noWrap>Author</Typography>
              </TableCell>
              <TableCell>{value?.source?.author}</TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography noWrap>Jenkins</Typography>
              </TableCell>
              <TableCell>
                <MaterialLink target="_blank" href={value?.url}>
                  View on Jenkins{' '}
                  <ExternalLinkIcon className={classes.externalLinkIcon} />
                </MaterialLink>
              </TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                {/* TODO: be SCM agnostic */}
                <Typography noWrap>GitHub</Typography>
              </TableCell>
              <TableCell>
                <MaterialLink target="_blank" href={value?.source?.url}>
                  View on GitHub{' '}
                  <ExternalLinkIcon className={classes.externalLinkIcon} />
                </MaterialLink>
              </TableCell>
            </TableRow>
          </TableBody>
        </Table>
      </TableContainer>
    </div>
  );
}
Example #6
Source File: WorkflowRunDetails.tsx    From backstage with Apache License 2.0 5 votes vote down vote up
JobListItem = ({
  job,
  className,
  entity,
}: {
  job: Job;
  className: string;
  entity: Entity;
}) => {
  const classes = useStyles();
  return (
    <Accordion TransitionProps={{ unmountOnExit: true }} className={className}>
      <AccordionSummary
        expandIcon={<ExpandMoreIcon />}
        aria-controls={`panel-${name}-content`}
        id={`panel-${name}-header`}
        IconButtonProps={{
          className: classes.button,
        }}
      >
        <Typography variant="button">
          {job.name} ({getElapsedTime(job.started_at, job.completed_at)})
        </Typography>
      </AccordionSummary>
      <AccordionDetails className={classes.accordionDetails}>
        <TableContainer>
          <Table>
            {job.steps.map(step => (
              <StepView key={step.number} step={step} />
            ))}
          </Table>
        </TableContainer>
      </AccordionDetails>
      {job.status === 'queued' || job.status === 'in_progress' ? (
        <WorkflowRunLogs runId={job.id} inProgress entity={entity} />
      ) : (
        <WorkflowRunLogs runId={job.id} inProgress={false} entity={entity} />
      )}
    </Accordion>
  );
}
Example #7
Source File: RecentGames.tsx    From planning-poker with MIT License 5 votes vote down vote up
RecentGames = () => {
  const history = useHistory();
  const [recentGames, setRecentGames] = useState<Game[] | undefined>(undefined);

  useEffect(() => {
    async function fetchData() {
      const games = await getPlayerRecentGames();
      if (games) {
        setRecentGames(games);
      }
    }
    fetchData();
  }, []);

  const isEmptyRecentGames = (): boolean => {
    if (!recentGames) {
      return true;
    }
    if (recentGames && recentGames.length === 0) {
      return true;
    }
    return false;
  };

  return (
    <Card variant='outlined' className='RecentGamesCard'>
      <CardHeader
        className='RecentGamesCardTitle'
        title='Recent Session'
        titleTypographyProps={{ variant: 'h6', noWrap: true }}
      />
      <CardContent className='RecentGamesCardContent'>
        {isEmptyRecentGames() && (
          <Typography variant='body2'>No recent sessions found</Typography>
        )}
        {recentGames && recentGames.length > 0 && (
          <TableContainer className='RecentGamesTableContainer'>
            <Table stickyHeader>
              <TableHead>
                <TableRow>
                  <TableCell>Name</TableCell>
                  <TableCell>Created By</TableCell>
                  <TableCell></TableCell>
                </TableRow>
              </TableHead>
              <TableBody>
                {recentGames.map((recentGame) => (
                  <TableRow
                    hover
                    key={recentGame.id}
                    className='RecentGamesTableRow'
                    onClick={() => history.push(`/game/${recentGame.id}`)}
                  >
                    <TableCell>{recentGame.name}</TableCell>
                    <TableCell align='left'>{recentGame.createdBy}</TableCell>
                    <TableCell align='left'></TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
          </TableContainer>
        )}
      </CardContent>
    </Card>
  );
}
Example #8
Source File: TodosTable.tsx    From postgres-nest-react-typescript-boilerplate with GNU General Public License v3.0 5 votes vote down vote up
TodosTable: FC<ITodoTable> = ({
  data,
  header,
  headerStyle,
  rowStyle,
  placeHolder,
  isLoading,
  onDeleteTodo,
  onCompleteTodo,
  stickyHeader = true
}) => {
  const classes = useStyles();

  return (
    <TableContainer className={classes.root} component={Paper} elevation={6}>
      <Table
        stickyHeader={stickyHeader}
        style={{ maxHeight: '100%' }}
        aria-label="sticky table"
      >
        <TableHeader data={header} headerStyle={headerStyle} />
        <TableBody>
          {data.length ? (
            data.map((todo) => {
              return (
                <Row
                  data={todo}
                  rowStyle={rowStyle}
                  onDeleteTodo={(e, id) => onDeleteTodo(e, id)}
                  onCompleteTodo={(e, checked, id) =>
                    onCompleteTodo(e, checked, id)
                  }
                />
              );
            })
          ) : (
            <RowPlaceHolder
              placeHolder={
                isLoading
                  ? 'Loading...'
                  : placeHolder || 'Put your place holder here'
              }
              colSpan={header.length}
              rowStyle={rowStyle}
            />
          )}
        </TableBody>
      </Table>
    </TableContainer>
  );
}
Example #9
Source File: Summary.tsx    From backstage with Apache License 2.0 5 votes vote down vote up
export function Summary() {
  const { releaseStats } = useReleaseStatsContext();
  const { summary } = getSummary({ releaseStats });
  const classes = useStyles();

  return (
    <Box margin={1}>
      <Typography variant="h4">Summary</Typography>

      <Typography variant="body2">
        <strong>Total releases</strong>: {summary.totalReleases}
      </Typography>

      <TableContainer>
        <Table size="small" className={classes.table}>
          <TableHead>
            <TableRow>
              <TableCell />
              <TableCell>Patches</TableCell>
              <TableCell>Patches per release</TableCell>
            </TableRow>
          </TableHead>

          <TableBody>
            <TableRow>
              <TableCell component="th" scope="row">
                Release Candidate
              </TableCell>
              <TableCell>{summary.totalCandidatePatches}</TableCell>
              <TableCell>
                {getDecimalNumber(
                  summary.totalCandidatePatches / summary.totalReleases,
                )}
              </TableCell>
            </TableRow>

            <TableRow>
              <TableCell component="th" scope="row">
                Release Version
              </TableCell>
              <TableCell>{summary.totalVersionPatches}</TableCell>
              <TableCell>
                {getDecimalNumber(
                  summary.totalVersionPatches / summary.totalReleases,
                )}
              </TableCell>
            </TableRow>

            <TableRow>
              <TableCell component="th" scope="row">
                Total
              </TableCell>
              <TableCell>
                {summary.totalCandidatePatches + summary.totalVersionPatches}
              </TableCell>
              <TableCell>
                {getDecimalNumber(
                  (summary.totalCandidatePatches +
                    summary.totalVersionPatches) /
                    summary.totalReleases,
                )}
              </TableCell>
            </TableRow>
          </TableBody>
        </Table>
      </TableContainer>
    </Box>
  );
}
Example #10
Source File: QueryResults.tsx    From SeeQR with MIT License 5 votes vote down vote up
QueryResults = ({ results }: QueryResultsProps) => {
  if (!results || !results.length) return null;

  const [page, setPage] = React.useState(0);
  const rowsPerPage = 10;

  // reset page to 1 if page is further than it could reasonable be. e.g. if
  // user edits a query that had many pages of results while being in the last
  // page and new query has a single page
  if (page * rowsPerPage > results.length) setPage(0);

  const columns = buildColumns(results[0]);

  const handleChangePage = (event: unknown, newPage: number) => {
    setPage(newPage);
  };

  // if there are performance issues, look into https://material-ui.com/components/tables/#virtualized-table
  return (
    <DarkPaperFull>
      <TableContainer>
        <Table>
          <TableHead>
            <TableRow>
              {columns.map((column) => (
                <TableCell key={column.name} align={column.align}>
                  <strong>{column.name}</strong>
                </TableCell>
              ))}
            </TableRow>
          </TableHead>
          <TableBody>
            {results
              .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
              .map((row) => (
                <TableRow
                  hover
                  role="checkbox"
                  tabIndex={-1}
                  key={Object.values(row).join()}
                >
                  {columns.map((column) => (
                    <StyledCell
                      align={column.align}
                      key={`${column.name}_${row[column.name]}`}
                    >
                      {(row[column.name] as any)?.toString()}
                    </StyledCell>
                  ))}
                </TableRow>
              ))}
          </TableBody>
        </Table>
      </TableContainer>
      <TablePagination
        // if there is only one option the dropdown is not displayed
        rowsPerPageOptions={[10]}
        component="div"
        count={results.length}
        rowsPerPage={rowsPerPage}
        page={page}
        onChangePage={handleChangePage}
      />
    </DarkPaperFull>
  );
}
Example #11
Source File: QuerySummary.tsx    From SeeQR with MIT License 5 votes vote down vote up
FlexChild = styled(TableContainer)`
  flex: 0 0 auto;
`
Example #12
Source File: MetricsTable.tsx    From abacus with GNU General Public License v2.0 5 votes vote down vote up
MetricDetail = ({ metric: metricInitial }: { metric: Metric }) => {
  const classes = useMetricDetailStyles()

  const {
    isLoading,
    data: metric,
    error,
  } = useDataSource(() => MetricsApi.findById(metricInitial.metricId), [metricInitial.metricId])
  useDataLoadingError(error)

  const isReady = !isLoading && !error

  return (
    <>
      {!isReady && <LinearProgress />}
      {isReady && metric && (
        <TableContainer className={classes.root}>
          <Table>
            <TableBody>
              <TableRow>
                <TableCell className={classes.headerCell}>Higher is Better:</TableCell>
                <TableCell className={classes.dataCell}>{formatBoolean(metric.higherIsBetter)}</TableCell>
              </TableRow>
              <TableRow>
                <TableCell className={classes.headerCell}>Parameters:</TableCell>
                <TableCell className={classes.dataCell}>
                  <div className={classes.pre}>
                    {JSON.stringify(
                      metric.parameterType === 'conversion' ? metric.eventParams : metric.revenueParams,
                      null,
                      4,
                    )}
                  </div>
                </TableCell>
              </TableRow>
            </TableBody>
          </Table>
        </TableContainer>
      )}
    </>
  )
}
Example #13
Source File: CallStatusTable.tsx    From twilio-voice-notification-app with Apache License 2.0 5 votes vote down vote up
CallStatusTable: React.FC<CallStatusTableProps> = ({
  recipients,
  meta,
  loading,
  pageCount,
  rowsPerPage,
  setRowsPerPage,
  currentPage,
  setCurrentPage,
}) => {
  const rowsPerPageOptions = useRowsPerPageOptions(meta?.total);

  const onChangeRowsPerPage = useCallback(
    (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
      const rows = parseInt(event.target.value, 10);
      setRowsPerPage(rows);
      setCurrentPage(0);
    },
    [setRowsPerPage, setCurrentPage]
  );

  const onChangePagination = useCallback(
    (event: unknown, value: number) => {
      setCurrentPage(value);
    },
    [setCurrentPage]
  );

  return (
    <>
      {recipients && recipients.length > 0 && (
        <>
          <TableContainer
            component={Paper}
            style={{
              marginBottom: loading ? '0' : '4px',
            }}
          >
            <Table
              aria-label="Notification recipients calls details and statuses"
              size="small"
              data-testid={RECIPIENTS_TABLE_TEST_ID}
            >
              <TableHead>
                <TableRow>
                  <TableCell>Number</TableCell>
                  <TableCell>Status</TableCell>
                </TableRow>
              </TableHead>
              <TableBody>
                {recipients?.map((recipient) => (
                  <TableRow key={recipient.callSid}>
                    <TableCell>{recipient.to}</TableCell>
                    <TableCell>{recipient.status}</TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
          </TableContainer>
          {loading && <LinearProgress />}
        </>
      )}
      {pageCount > 0 && meta.total && (
        <TablePagination
          data-testid={PAGINATOR_TEST_ID}
          rowsPerPageOptions={rowsPerPageOptions}
          component="div"
          page={currentPage}
          count={meta.total}
          rowsPerPage={rowsPerPage}
          onChangePage={onChangePagination}
          onChangeRowsPerPage={onChangeRowsPerPage}
        />
      )}
    </>
  );
}
Example #14
Source File: NumbersTable.tsx    From twilio-voice-notification-app with Apache License 2.0 5 votes vote down vote up
NumbersTable: React.FC<NumbersTableProps> = ({
  headers,
  rows,
  data,
}) => {
  const classes = useNumbersTableStyles();
  const {
    page,
    rowsPerPage,
    handleChangePage,
    handleChangeRowsPerPage,
    paginatedData,
    rowsPerPageOptions,
  } = usePagination(data);

  const rowCells = useMemo(() => {
    return paginatedData.map(
      (rowData: { [key: string]: string }, idx: number) => (
        <TableRow key={idx}>
          {rows.map((cell: string, index: number) => (
            <TableCell key={`${idx}-${index}`}>{rowData[cell]}</TableCell>
          ))}
        </TableRow>
      )
    );
  }, [paginatedData, rows]);

  return (
    <>
      <TableContainer component={Box} className={classes.tableContainer}>
        <Table stickyHeader size="small">
          <TableHead>
            <TableRow>
              {headers.map((title: string, idx: number) => (
                <StyledTableCell key={idx}>{title}</StyledTableCell>
              ))}
            </TableRow>
          </TableHead>
          <TableBody>{rowCells}</TableBody>
        </Table>
      </TableContainer>
      <TablePagination
        rowsPerPageOptions={rowsPerPageOptions}
        component="div"
        count={data.length}
        rowsPerPage={rowsPerPage}
        page={page}
        onChangePage={handleChangePage}
        onChangeRowsPerPage={handleChangeRowsPerPage}
      />
    </>
  );
}
Example #15
Source File: HealthIndicatorTable.tsx    From abacus with GNU General Public License v2.0 5 votes vote down vote up
export default function HealthIndicatorTable({
  className,
  indicators,
}: {
  className?: string
  indicators: HealthIndicator[]
}): JSX.Element {
  const classes = useStyles()
  const decorationClasses = useDecorationStyles()
  return (
    <TableContainer className={className}>
      <Table className={classes.table} aria-label='simple table'>
        <TableHead>
          <TableRow>
            <TableCell>Name</TableCell>
            <TableCell>Unit</TableCell>
            <TableCell>Value</TableCell>
            <TableCell>{/* Indication Emoji */}</TableCell>
            <TableCell>Indication</TableCell>
            <TableCell>Reason</TableCell>
            <TableCell>Recommendation</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {indicators.map((indicator) => (
            <TableRow key={indicator.name}>
              <TableCell scope='row'>
                <Link href={indicator.link} target='_blank'>
                  {indicator.name}
                </Link>
              </TableCell>
              <TableCell scope='row' className={clsx(classes.monospace, classes.deemphasized, classes.nowrap)}>
                {indicator.unit === HealthIndicatorUnit.Pvalue ? (
                  <Tooltip title='The smaller the p-value the more likely there is an issue.'>
                    <span className={decorationClasses.tooltipped}>p-value</span>
                  </Tooltip>
                ) : (
                  <span>{indicator.unit}</span>
                )}
              </TableCell>
              <TableCell scope='row' className={clsx(classes.monospace, classes.deemphasized, classes.nowrap)}>
                {indicator.value.toFixed(4)}
              </TableCell>
              <TableCell scope='row'>
                <span>{severityEmoji[indicator.indication.severity]}</span>
              </TableCell>
              <TableCell
                scope='row'
                className={clsx(
                  classes.indication,
                  classes[indicationSeverityClassSymbol(indicator.indication.severity)],
                  classes.monospace,
                  classes.nowrap,
                )}
              >
                <span>{indicator.indication.code}</span>
              </TableCell>
              <TableCell scope='row' className={clsx(classes.monospace, classes.deemphasized, classes.nowrap)}>
                {indicator.indication.reason}
              </TableCell>
              <TableCell scope='row' className={clsx(classes.deemphasized, classes.recommendation)}>
                <Typography variant='body1'>{indicator.indication.recommendation}</Typography>
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  )
}
Example #16
Source File: DialogBody.tsx    From backstage with Apache License 2.0 5 votes vote down vote up
export function DialogBody() {
  const classes = useStyles();
  const { stats } = useGetStats();
  const { project } = useProjectContext();

  if (stats.error) {
    return (
      <Alert severity="error">Unexpected error: {stats.error.message}</Alert>
    );
  }

  if (stats.loading) {
    return <Progress />;
  }

  if (!stats.value) {
    return <Alert severity="error">Couldn't find any stats :(</Alert>;
  }

  const { allReleases, allTags } = stats.value;
  const { mappedReleases } = getMappedReleases({ allReleases, project });
  const { releaseStats } = getReleaseStats({
    mappedReleases,
    allTags,
    project,
  });

  return (
    <ReleaseStatsContext.Provider value={{ releaseStats }}>
      <Info />

      <TableContainer>
        <Table className={classes.table} size="small">
          <TableHead>
            <TableRow>
              <TableCell />
              <TableCell>Release</TableCell>
              <TableCell>Created at</TableCell>
              <TableCell># candidate patches</TableCell>
              <TableCell># release patches</TableCell>
            </TableRow>
          </TableHead>

          <TableBody>
            {Object.entries(releaseStats.releases).map(
              ([baseVersion, releaseStat], index) => {
                return (
                  <Row
                    key={`row-${index}`}
                    baseVersion={baseVersion}
                    releaseStat={releaseStat}
                  />
                );
              },
            )}
          </TableBody>
        </Table>

        {(releaseStats.unmappableTags.length > 0 ||
          releaseStats.unmatchedTags.length > 0 ||
          releaseStats.unmatchedReleases.length > 0) && <Warn />}
      </TableContainer>
    </ReleaseStatsContext.Provider>
  );
}
Example #17
Source File: WorkflowRunDetails.tsx    From backstage with Apache License 2.0 4 votes vote down vote up
WorkflowRunDetails = ({ entity }: { entity: Entity }) => {
  const config = useApi(configApiRef);
  const projectName = getProjectNameFromEntity(entity);

  // TODO: Get github hostname from metadata annotation
  const hostname = readGitHubIntegrationConfigs(
    config.getOptionalConfigArray('integrations.github') ?? [],
  )[0].host;
  const [owner, repo] = (projectName && projectName.split('/')) || [];
  const details = useWorkflowRunsDetails({ hostname, owner, repo });
  const jobs = useWorkflowRunJobs({ hostname, owner, repo });

  const classes = useStyles();

  if (details.error && details.error.message) {
    return (
      <Typography variant="h6" color="error">
        Failed to load build, {details.error.message}
      </Typography>
    );
  } else if (details.loading) {
    return <LinearProgress />;
  }
  return (
    <div className={classes.root}>
      <Box mb={3}>
        <Breadcrumbs aria-label="breadcrumb">
          <Link to="..">Workflow runs</Link>
          <Typography>Workflow run details</Typography>
        </Breadcrumbs>
      </Box>
      <TableContainer component={Paper} className={classes.table}>
        <Table>
          <TableBody>
            <TableRow>
              <TableCell>
                <Typography noWrap>Branch</Typography>
              </TableCell>
              <TableCell>{details.value?.head_branch}</TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography noWrap>Message</Typography>
              </TableCell>
              <TableCell>{details.value?.head_commit?.message}</TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography noWrap>Commit ID</Typography>
              </TableCell>
              <TableCell>{details.value?.head_commit?.id}</TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography noWrap>Workflow</Typography>
              </TableCell>
              <TableCell>{details.value?.name}</TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography noWrap>Status</Typography>
              </TableCell>
              <TableCell>
                <WorkflowRunStatus
                  status={details.value?.status || undefined}
                  conclusion={details.value?.conclusion || undefined}
                />
              </TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography noWrap>Author</Typography>
              </TableCell>
              <TableCell>{`${details.value?.head_commit?.author?.name} (${details.value?.head_commit?.author?.email})`}</TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography noWrap>Links</Typography>
              </TableCell>
              <TableCell>
                {details.value?.html_url && (
                  <MaterialLink target="_blank" href={details.value.html_url}>
                    Workflow runs on GitHub{' '}
                    <ExternalLinkIcon className={classes.externalLinkIcon} />
                  </MaterialLink>
                )}
              </TableCell>
            </TableRow>
            <TableRow>
              <TableCell colSpan={2}>
                <Typography noWrap>Jobs</Typography>
                {jobs.loading ? (
                  <CircularProgress />
                ) : (
                  <JobsList jobs={jobs.value} entity={entity} />
                )}
              </TableCell>
            </TableRow>
          </TableBody>
        </Table>
      </TableContainer>
    </div>
  );
}
Example #18
Source File: UserBookings.tsx    From office-booker with MIT License 4 votes vote down vote up
UserBookings: React.FC<RouteComponentProps<{ email: string }>> = (props) => {
  // Global state
  const { state, dispatch } = useContext(AppContext);
  const { user } = state;

  // Local state
  const [loading, setLoading] = useState(true);
  const [selectedUser, setSelectedUser] = useState<User | undefined>();
  const [bookings, setBookings] = useState<Booking[] | undefined>();
  const [bookingToCancel, setBookingToCancel] = useState<undefined | Booking>();
  const [sortedBookings, setSortedBookings] = useState<Booking[] | undefined>();

  const [sortBy, setSortBy] = useState<keyof Booking>('date');
  const [sortOrder, setSortOrder] = useState<SortOrder>('asc');

  // Theme
  const theme = useTheme();
  const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));

  // Effects
  useEffect(() => {
    if (props.email) {
      getBookings({ user: props.email })
        .then((data) => {
          // Split for previous and upcoming
          setBookings(data);
        })
        .catch((err) => {
          // Handle errors
          setLoading(false);

          dispatch({
            type: 'SET_ALERT',
            payload: {
              message: formatError(err),
              color: 'error',
            },
          });
        });
    }
  }, [props.email, dispatch]);

  useEffect(() => {
    if (user && !user.permissions.canViewUsers) {
      // No permissions - Bounce to home page
      navigate('/');
    }
  }, [user]);

  useEffect(() => {
    if (user) {
      // Get selected user
      getUser(props.email || '')
        .then((selectedUser) => setSelectedUser(selectedUser))
        .catch((err) => {
          // Handle errors
          setLoading(false);

          dispatch({
            type: 'SET_ALERT',
            payload: {
              message: formatError(err),
              color: 'error',
            },
          });
        });
    }
  }, [user, props.email, dispatch]);

  useEffect(() => {
    if (bookings) {
      // Sort it!
      setSortedBookings(sortData([...bookings], sortBy, sortOrder));
    }
  }, [bookings, sortBy, sortOrder]);

  useEffect(() => {
    if (bookings) {
      // Wait for global state to be ready
      setLoading(false);
    }
  }, [bookings]);

  // Handlers
  const handleSort = (key: keyof Booking) => {
    if (key === sortBy) {
      setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc');
    } else {
      setSortBy(key);
    }
  };

  const getAllBookings = useCallback(() => {
    if (state.user) {
      getBookings({ user: state.user.email })
        .then((data) => {
          // Split for previous and upcoming
          setBookings(data);
        })
        .catch((err) => {
          // Handle errors
          setLoading(false);

          dispatch({
            type: 'SET_ALERT',
            payload: {
              message: formatError(err),
              color: 'error',
            },
          });
        });
    }
  }, [state.user, dispatch]);

  const handleCancelBooking = (booking: Booking) => {
    cancelBooking(booking.id, booking.user)
      .then(() => {
        // Clear selected booking
        setBookingToCancel(undefined);

        // Retrieve updated bookings
        getAllBookings();

        // Show confirmation alert
        dispatch({
          type: 'SET_ALERT',
          payload: {
            message: 'Booking cancelled',
            color: 'success',
          },
        });
      })
      .catch((err) =>
        dispatch({
          type: 'SET_ALERT',
          payload: {
            message: formatError(err),
            color: 'error',
          },
        })
      );
  };

  // Render
  if (!user) {
    return null;
  }

  return (
    <AdminLayout currentRoute="users">
      <UserBookingsStyles>
        {loading || !selectedUser ? (
          <Loading />
        ) : (
          <>
            <h3>User Bookings</h3>

            <Paper square className="table-container">
              <h4>{selectedUser.email}</h4>
              <TableContainer className="table">
                <Table>
                  <TableHead>
                    <TableRow>
                      <TableCell className="table-header">
                        <TableSortLabel
                          active={sortBy === 'office'}
                          direction={sortOrder}
                          onClick={() => handleSort('office')}
                        >
                          Office
                        </TableSortLabel>
                      </TableCell>
                      <TableCell className="table-header">
                        <TableSortLabel
                          active={sortBy === 'date'}
                          direction={sortOrder}
                          onClick={() => handleSort('date')}
                        >
                          Date
                        </TableSortLabel>
                      </TableCell>
                      <TableCell className="table-header">
                        <TableSortLabel
                          active={sortBy === 'parking'}
                          direction={sortOrder}
                          onClick={() => handleSort('parking')}
                        >
                          Parking
                        </TableSortLabel>
                      </TableCell>
                      <TableCell />
                    </TableRow>
                  </TableHead>
                  <TableBody>
                    {sortedBookings && sortedBookings.length > 0 ? (
                      sortedBookings.map((booking, index) => {
                        const parsedDate = parseISO(booking.date);

                        return (
                          <TableRow key={index}>
                            <TableCell>{booking.office.name}</TableCell>
                            <TableCell>
                              {' '}
                              {format(
                                parse(booking.date, 'yyyy-MM-dd', new Date(), DATE_FNS_OPTIONS),
                                'do LLLL yyyy',
                                DATE_FNS_OPTIONS
                              )}
                            </TableCell>
                            <TableCell>{booking.parking ? 'Yes' : 'No'}</TableCell>
                            {isToday(parsedDate) || !isPast(parsedDate) ? (
                              <TableCell align="right">
                                <div className="btn-container">
                                  <OurButton
                                    type="submit"
                                    variant="contained"
                                    color="secondary"
                                    size="small"
                                    onClick={() => setBookingToCancel(booking)}
                                  >
                                    Cancel
                                  </OurButton>
                                </div>
                              </TableCell>
                            ) : (
                              <TableCell />
                            )}
                          </TableRow>
                        );
                      })
                    ) : (
                      <TableRow>
                        <TableCell>No bookings found</TableCell>
                        <TableCell />
                      </TableRow>
                    )}
                  </TableBody>
                </Table>
              </TableContainer>
            </Paper>
          </>
        )}

        {bookingToCancel && (
          <Dialog fullScreen={fullScreen} open={true} onClose={() => setBookingToCancel(undefined)}>
            <DialogTitle>{'Are you sure you want to cancel this booking?'}</DialogTitle>
            <DialogContent>
              <DialogContentText>
                Booking for <strong>{bookingToCancel.user}</strong> on{' '}
                <strong>{format(parseISO(bookingToCancel.date), 'do LLLL')}</strong> for{' '}
                <strong>{bookingToCancel.office.name}</strong>
              </DialogContentText>
            </DialogContent>
            <DialogActions>
              <Button onClick={() => setBookingToCancel(undefined)} color="primary" autoFocus>
                No
              </Button>
              <Button
                autoFocus
                onClick={() => handleCancelBooking(bookingToCancel)}
                color="primary"
              >
                Yes
              </Button>
            </DialogActions>
          </Dialog>
        )}
      </UserBookingsStyles>
    </AdminLayout>
  );
}
Example #19
Source File: ActionsPage.tsx    From backstage with Apache License 2.0 4 votes vote down vote up
ActionsPage = () => {
  const api = useApi(scaffolderApiRef);
  const classes = useStyles();
  const { loading, value, error } = useAsync(async () => {
    return api.listActions();
  });

  if (loading) {
    return <Progress />;
  }

  if (error) {
    return (
      <ErrorPage
        statusMessage="Failed to load installed actions"
        status="500"
      />
    );
  }

  const formatRows = (input: JSONSchema7) => {
    const properties = input.properties;
    if (!properties) {
      return undefined;
    }

    return Object.entries(properties).map(entry => {
      const [key] = entry;
      const props = entry[1] as unknown as JSONSchema7;
      const codeClassname = classNames(classes.code, {
        [classes.codeRequired]: input.required?.includes(key),
      });

      return (
        <TableRow key={key}>
          <TableCell>
            <div className={codeClassname}>{key}</div>
          </TableCell>
          <TableCell>{props.title}</TableCell>
          <TableCell>{props.description}</TableCell>
          <TableCell>
            <span className={classes.code}>{props.type}</span>
          </TableCell>
        </TableRow>
      );
    });
  };

  const renderTable = (input: JSONSchema7) => {
    if (!input.properties) {
      return undefined;
    }
    return (
      <TableContainer component={Paper}>
        <Table size="small">
          <TableHead>
            <TableRow>
              <TableCell>Name</TableCell>
              <TableCell>Title</TableCell>
              <TableCell>Description</TableCell>
              <TableCell>Type</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>{formatRows(input)}</TableBody>
        </Table>
      </TableContainer>
    );
  };

  const renderTables = (name: string, input?: JSONSchema7Definition[]) => {
    if (!input) {
      return undefined;
    }

    return (
      <>
        <Typography variant="h6">{name}</Typography>
        {input.map((i, index) => (
          <div key={index}>{renderTable(i as unknown as JSONSchema7)}</div>
        ))}
      </>
    );
  };

  const items = value?.map(action => {
    if (action.id.startsWith('legacy:')) {
      return undefined;
    }

    const oneOf = renderTables('oneOf', action.schema?.input?.oneOf);
    return (
      <Box pb={4} key={action.id}>
        <Typography variant="h4" className={classes.code}>
          {action.id}
        </Typography>
        <Typography>{action.description}</Typography>
        {action.schema?.input && (
          <Box pb={2}>
            <Typography variant="h5">Input</Typography>
            {renderTable(action.schema.input)}
            {oneOf}
          </Box>
        )}
        {action.schema?.output && (
          <Box pb={2}>
            <Typography variant="h5">Output</Typography>
            {renderTable(action.schema.output)}
          </Box>
        )}
      </Box>
    );
  });

  return (
    <Page themeId="home">
      <Header
        pageTitleOverride="Create a New Component"
        title="Installed actions"
        subtitle="This is the collection of all installed actions"
      />
      <Content>{items}</Content>
    </Page>
  );
}
Example #20
Source File: SignDeployPage.tsx    From signer with Apache License 2.0 4 votes vote down vote up
render() {
    if (this.state.deployToSign && this.props.accountManager.isUnLocked) {
      const deployId = this.props.signingContainer.deployToSign?.id;
      return (
        <div
          style={{
            flexGrow: 1,
            marginTop: '-30px',
            width: '100vw'
          }}
        >
          <Typography align={'center'} variant={'h6'}>
            Signature Request
          </Typography>
          <TableContainer>
            <Table style={{ width: '90%' }}>
              <TableBody>
                {/* 
                  Displays the data generic to all deploys
                */}
                {this.state.genericRows.map(row => (
                  <TooltippedTableRow data={row} />
                ))}
                {/* 
                  Deploy Specific Arguments
                  Special handling for native transfers
                */}
                {this.state.genericRows.some(
                  row => row.key === 'Deploy Type' && row.value === 'Transfer'
                ) ? (
                  <>
                    <TableRow>
                      <TableCell
                        component="th"
                        scope="row"
                        style={{ fontWeight: 'bold' }}
                        colSpan={2}
                        align="center"
                      >
                        Transfer Data
                      </TableCell>
                    </TableRow>
                    <TableRow>
                      <TableCell
                        style={{ paddingBottom: 0, paddingTop: 0 }}
                        colSpan={2}
                      >
                        <Table size="small">
                          <TableBody>
                            {this.state.deploySpecificRows.map(row => (
                              <TooltippedTableRow data={row} />
                            ))}
                          </TableBody>
                        </Table>
                      </TableCell>
                    </TableRow>
                  </>
                ) : (
                  /**
                   *  Deploy specific arguments
                   */
                  <>
                    <TableRow>
                      <TableCell
                        component="th"
                        scope="row"
                        style={{ fontWeight: 'bold' }}
                      >
                        Contract Arguments
                      </TableCell>
                      <TableCell align="right">
                        <IconButton
                          size="small"
                          onClick={() =>
                            this.setState({
                              argsExpanded: !this.state.argsExpanded
                            })
                          }
                        >
                          {this.state.argsExpanded ? (
                            <KeyboardArrowUpIcon />
                          ) : (
                            <KeyboardArrowDownIcon />
                          )}
                        </IconButton>
                      </TableCell>
                    </TableRow>
                    <TableRow>
                      <TableCell
                        style={{ paddingBottom: 0, paddingTop: 0 }}
                        colSpan={2}
                      >
                        <Collapse
                          in={this.state.argsExpanded}
                          timeout="auto"
                          unmountOnExit
                        >
                          <Table size="small">
                            <TableBody>
                              {this.state.deploySpecificRows.map(row => (
                                <TooltippedTableRow data={row} />
                              ))}
                            </TableBody>
                          </Table>
                        </Collapse>
                      </TableCell>
                    </TableRow>
                  </>
                )}
              </TableBody>
            </Table>
          </TableContainer>
          <Box mt={8}>
            <Grid
              container
              style={{ marginTop: '-50px' }}
              spacing={4}
              justify={'center'}
              alignItems={'center'}
            >
              <Grid item>
                <Button
                  variant="contained"
                  color="secondary"
                  onClick={async () => {
                    await this.props.signingContainer.cancel(deployId!);
                    await this.props.popupContainer.callClosePopup();
                    // This is required in the case that the user avoids the popup and instead
                    // interacts with the default extension window.
                    window.close();
                  }}
                >
                  Cancel
                </Button>
              </Grid>
              <Grid item>
                <Button
                  onClick={async () => {
                    await this.props.signingContainer.signDeploy(deployId!);
                    await this.props.popupContainer.callClosePopup();
                    // This is required in the case that the user avoids the popup and instead
                    // interacts with the default extension window.
                    window.close();
                  }}
                  variant="contained"
                  color="primary"
                  style={{
                    backgroundColor: 'var(--cspr-dark-blue)'
                  }}
                >
                  Sign
                </Button>
              </Grid>
            </Grid>
          </Box>
        </div>
      );
    } else {
      return <Redirect to={Pages.Home} />;
    }
  }
Example #21
Source File: Vulnerability.tsx    From crossfeed with Creative Commons Zero v1.0 Universal 4 votes vote down vote up
Vulnerability: React.FC = () => {
  const { vulnerabilityId } = useParams();
  const { apiGet, apiPut } = useAuthContext();
  const [vulnerability, setVulnerability] = useState<VulnerabilityType>();
  const [comment, setComment] = useState<string>('');
  const [showCommentForm, setShowCommentForm] = useState<boolean>(false);
  const [menuAnchor, setMenuAnchor] = React.useState<null | HTMLElement>(null);
  const classes = useStyles();
  const history = useHistory();

  const formatDate = (date: string) => {
    return format(parseISO(date), 'MM-dd-yyyy');
  };

  const fetchVulnerability = useCallback(async () => {
    try {
      const result = await apiGet<VulnerabilityType>(
        `/vulnerabilities/${vulnerabilityId}`
      );
      setVulnerability(result);
    } catch (e) {
      console.error(e);
    }
  }, [vulnerabilityId, apiGet]);

  const updateVulnerability = async (body: { [key: string]: string }) => {
    try {
      if (!vulnerability) return;
      const res = await apiPut<VulnerabilityType>(
        '/vulnerabilities/' + vulnerability.id,
        {
          body: body
        }
      );
      setVulnerability({
        ...vulnerability,
        state: res.state,
        substate: res.substate,
        actions: res.actions
      });
    } catch (e) {
      console.error(e);
    }
  };

  useEffect(() => {
    fetchVulnerability();
  }, [fetchVulnerability]);

  if (!vulnerability) return <></>;

  const references = vulnerability.references.map((ref) => ref);
  if (vulnerability.cve)
    references.unshift({
      name: 'NIST National Vulnerability Database',
      url: `https://nvd.nist.gov/vuln/detail/${vulnerability.cve}`,
      source: '',
      tags: []
    });

  const states = [
    'unconfirmed',
    'exploitable',
    'false-positive',
    'accepted-risk',
    'remediated'
  ];
  interface dnstwist {
    'domain-name': string;
    fuzzer: string;
    'dns-a'?: string;
    'dns-aaas'?: string;
    'dns-mx'?: string;
    'dns-ns'?: string;
    'date-first-observed'?: string;
  }

  return (
    <>
      {/* <Alert severity="info">
        This vulnerability is found on 17 domains you have access to.
      </Alert> */}
      <div className={classes.root}>
        <p>
          <Link
            to="# "
            onClick={() => history.goBack()}
            className={classes.backLink}
          >
            <ChevronLeft
              style={{
                height: '100%',
                verticalAlign: 'middle',
                marginTop: '-2px'
              }}
            ></ChevronLeft>
            Go back
          </Link>
        </p>

        <div className={classes.contentWrapper}>
          <div className={classes.content}>
            <div
              className={classes.panel}
              style={{
                flex: '0 0 45%'
              }}
            >
              <Paper classes={{ root: classes.cardRoot }}>
                <div className={classes.title}>
                  <h4>{vulnerability.title}</h4>
                  <Button
                    aria-haspopup="true"
                    onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
                      setMenuAnchor(event.currentTarget)
                    }
                  >
                    <Flag
                      style={{
                        fontSize: '14px',
                        color: '#A9AEB1',
                        marginRight: '5px'
                      }}
                    ></Flag>
                    Mark Item <ArrowDropDown />
                  </Button>
                  <Menu
                    anchorEl={menuAnchor}
                    keepMounted
                    open={Boolean(menuAnchor)}
                    getContentAnchorEl={null}
                    onClose={() => setMenuAnchor(null)}
                    anchorOrigin={{
                      vertical: 'bottom',
                      horizontal: 'center'
                    }}
                    transformOrigin={{
                      vertical: 'top',
                      horizontal: 'center'
                    }}
                  >
                    {states.map((state) => (
                      <MenuItem
                        key={state}
                        onClick={() => {
                          updateVulnerability({
                            substate: state
                          });
                          setMenuAnchor(null);
                        }}
                        style={{ outline: 'none' }}
                      >
                        {stateMap[state]}
                      </MenuItem>
                    ))}
                  </Menu>
                </div>
                <Chip
                  style={{
                    marginLeft: '1.5rem'
                  }}
                  // icon={<Check></Check>}
                  label={`${vulnerability.state[0].toUpperCase()}${vulnerability.state.slice(
                    1
                  )} (${stateMap[vulnerability.substate]})`}
                  color={
                    vulnerability.state === 'open' ? 'secondary' : 'default'
                  }
                />
                <div className={classes.inner}>
                  <div className={classes.section}>
                    <h4 className={classes.subtitle}>Description</h4>
                    {vulnerability.description}
                  </div>
                  <div className={classes.section}>
                    <h4 className={classes.subtitle}>References</h4>
                    {references &&
                      references.map((ref, index) => (
                        <p key={index}>
                          <a
                            href={ref.url}
                            target="_blank"
                            rel="noopener noreferrer"
                          >
                            {ref.name ? ref.name : ref.url}
                          </a>
                          {ref.tags.length > 0
                            ? ' - ' + ref.tags.join(',')
                            : ''}
                        </p>
                      ))}
                  </div>
                  {vulnerability.source === 'hibp' && (
                    <div className={classes.section}>
                      <h4 className={classes.subtitle}>Data</h4>
                      <Table aria-label="simple table">
                        <TableHead>
                          <TableRow>
                            <TableCell>Exposed Emails</TableCell>
                            <TableCell align="right">Breaches</TableCell>
                          </TableRow>
                        </TableHead>
                        <TableBody>
                          {Object.keys(
                            vulnerability.structuredData['emails']
                          ).map((keyName, keyIndex) => (
                            <TableRow key={keyName}>
                              <TableCell component="th" scope="row">
                                {keyName}
                              </TableCell>
                              <TableCell align="right">
                                {vulnerability.structuredData['emails'][
                                  keyName
                                ].join(',  ')}
                              </TableCell>
                            </TableRow>
                          ))}
                        </TableBody>
                      </Table>
                    </div>
                  )}
                  {vulnerability.source === 'lookingGlass' && (
                    <div className={classes.section}>
                      <h4 className={classes.subtitle}>Data</h4>
                      <Table aria-label="simple table">
                        <TableHead>
                          <TableRow>
                            <TableCell>First Seen</TableCell>
                            <TableCell align="right">Last Seen</TableCell>
                            <TableCell align="right">Vuln Name</TableCell>
                            <TableCell align="right">Type</TableCell>
                          </TableRow>
                        </TableHead>
                        <TableBody>
                          {vulnerability.structuredData['lookingGlassData'].map(
                            (col: any) => (
                              <TableRow key={col.right_name}>
                                <TableCell component="th" scope="row">
                                  {formatDistanceToNow(
                                    parseISO(col.firstSeen)
                                  ) + ' ago'}
                                </TableCell>
                                <TableCell align="right">
                                  {formatDistanceToNow(parseISO(col.lastSeen)) +
                                    ' ago'}
                                </TableCell>
                                <TableCell align="right">
                                  {col.right_name}
                                </TableCell>
                                <TableCell align="right">
                                  {col.vulnOrMal}
                                </TableCell>
                              </TableRow>
                            )
                          )}
                        </TableBody>
                      </Table>
                    </div>
                  )}
                  {vulnerability.source === 'dnstwist' && (
                    <div className={classes.section}>
                      <h4 className={classes.subtitle}>Data</h4>
                      <TableContainer>
                        <Table aria-label="simple table">
                          <TableHead>
                            <TableRow>
                              <TableCell>Domain Name</TableCell>
                              <TableCell>IP Address / A Record</TableCell>
                              <TableCell>MX Record</TableCell>
                              <TableCell>NS Record</TableCell>
                              <TableCell>Date Observed</TableCell>
                              <TableCell>Fuzzer</TableCell>
                            </TableRow>
                          </TableHead>
                          <TableBody>
                            {vulnerability.structuredData['domains'].map(
                              (dom: dnstwist) => (
                                <TableRow key={dom['domain-name']}>
                                  <TableCell component="th" scope="row">
                                    {dom['domain-name']}
                                  </TableCell>
                                  <TableCell>{dom['dns-a']}</TableCell>
                                  <TableCell>{dom['dns-mx']}</TableCell>
                                  <TableCell>{dom['dns-ns']}</TableCell>
                                  <TableCell>
                                    {dom['date-first-observed']}
                                  </TableCell>
                                  <TableCell>{dom['fuzzer']}</TableCell>
                                </TableRow>
                              )
                            )}
                          </TableBody>
                        </Table>
                      </TableContainer>
                    </div>
                  )}
                </div>
              </Paper>
            </div>
            <div
              className={classes.panel}
              style={{
                flex: '0 0 30%'
              }}
            >
              <Paper className={classes.cardRoot}>
                <div className={classes.inner}>
                  <div className={classes.section}>
                    <h2 className={classes.subtitle}>Team notes</h2>
                    <button
                      onClick={() => {
                        setShowCommentForm(!showCommentForm);
                      }}
                      className={classes.linkSmall}
                    >
                      Add new note
                    </button>
                  </div>
                  {showCommentForm && (
                    <div>
                      <TextareaAutosize
                        style={{
                          width: '100%',
                          padding: 10,
                          marginBottom: '20px'
                        }}
                        rowsMin={4}
                        placeholder="Leave a Note"
                        onChange={(e) => setComment(e.target.value)}
                      />
                      <Button
                        onClick={() => {
                          updateVulnerability({
                            comment
                          });
                          setComment('');
                          setShowCommentForm(false);
                        }}
                        style={{
                          width: 150,
                          marginBottom: '20px'
                        }}
                        variant="contained"
                        color="secondary"
                      >
                        Save
                      </Button>
                    </div>
                  )}
                  {vulnerability.actions &&
                    vulnerability.actions
                      .filter((action) => action.type === 'comment')
                      .map((action, index) => (
                        <div className={classes.section} key={index}>
                          <h4
                            className={classes.subtitle}
                            style={{ fontSize: '16px', display: 'inline' }}
                          >
                            {action.userName}
                          </h4>
                          <span style={{ float: 'right', display: 'inline' }}>
                            {formatDistanceToNow(parseISO(action.date))} ago
                          </span>
                          <ReactMarkdown linkTarget="_blank">
                            {action.value || ''}
                          </ReactMarkdown>
                        </div>
                      ))}
                </div>
              </Paper>
              <Paper className={classes.cardRoot}>
                <div className={classes.inner}>
                  <div className={classes.section}>
                    <h2 className={classes.subtitle}>Vulnerability History</h2>
                  </div>
                  <Timeline
                    style={{
                      position: 'relative',
                      marginLeft: '-90%'
                    }}
                    align="left"
                  >
                    {vulnerability.actions &&
                      vulnerability.actions
                        .filter(
                          (action) =>
                            action.type === 'state-change' && action.substate
                        )
                        .map((action, index) => (
                          <TimelineItem key={index}>
                            <TimelineSeparator>
                              <TimelineDot />
                              <TimelineConnector />
                            </TimelineSeparator>{' '}
                            <TimelineContent>
                              State {action.automatic ? 'automatically ' : ''}
                              changed to {action.state} (
                              {stateMap[action.substate!].toLowerCase()})
                              {action.userName ? ' by ' + action.userName : ''}{' '}
                              <br></br>
                              <span
                                style={{
                                  color: '#A9AEB1'
                                }}
                              >
                                {formatDate(action.date)}
                              </span>
                            </TimelineContent>
                          </TimelineItem>
                        ))}

                    <TimelineItem>
                      <TimelineSeparator>
                        <TimelineDot />
                      </TimelineSeparator>
                      <TimelineContent>
                        Vulnerability opened<br></br>
                        <span
                          style={{
                            color: '#A9AEB1'
                          }}
                        >
                          {formatDate(vulnerability.createdAt)}
                        </span>
                      </TimelineContent>
                    </TimelineItem>
                  </Timeline>
                </div>
              </Paper>
              <Paper className={classes.cardRoot}>
                <div className={classes.inner}>
                  <div className={classes.section}>
                    <h2 className={classes.subtitle}>Provenance</h2>
                    <p>
                      <strong>Root Domain: </strong>
                      {vulnerability.domain.fromRootDomain}
                    </p>
                    <p>
                      <strong>Subdomain: </strong>
                      {vulnerability.domain.name} (
                      {vulnerability.domain.subdomainSource})
                    </p>
                    {vulnerability.service && (
                      <p>
                        <strong>Service/Port: </strong>
                        {vulnerability.service.service
                          ? vulnerability.service.service
                          : vulnerability.service.port}{' '}
                        ({vulnerability.service.serviceSource})
                      </p>
                    )}
                    {vulnerability.cpe && (
                      <>
                        <p>
                          <strong>Product: </strong>
                          {vulnerability.cpe}
                        </p>
                      </>
                    )}
                    <p>
                      <strong>Vulnerability: </strong>
                      {vulnerability.title} ({vulnerability.source})
                    </p>
                  </div>
                </div>
              </Paper>
              {vulnerability.source === 'hibp' && (
                <Paper className={classes.cardRoot}>
                  <div className={classes.inner}>
                    <div className={classes.section}>
                      <h2 className={classes.subtitle}>Breaches</h2>
                      <Table aria-label="simple table">
                        <TableHead>
                          <TableRow>
                            <TableCell>Breach Name</TableCell>
                            <TableCell align="right">Date Added</TableCell>
                          </TableRow>
                        </TableHead>
                        <TableBody>
                          {Object.keys(vulnerability.structuredData['breaches'])
                            .sort(
                              (a, b) =>
                                parseISO(
                                  vulnerability.structuredData['breaches'][b][
                                    'AddedDate'
                                  ]
                                ).getTime() -
                                parseISO(
                                  vulnerability.structuredData['breaches'][a][
                                    'AddedDate'
                                  ]
                                ).getTime()
                            )
                            .map((keyName, keyIndex) => (
                              <TableRow key={keyName}>
                                <TableCell component="th" scope="row">
                                  {keyName}
                                </TableCell>
                                <TableCell align="right">
                                  {formatDistanceToNow(
                                    parseISO(
                                      vulnerability.structuredData['breaches'][
                                        keyName
                                      ]['AddedDate']
                                    )
                                  ) + ' ago'}
                                </TableCell>
                              </TableRow>
                            ))}
                        </TableBody>
                      </Table>
                    </div>
                  </div>
                </Paper>
              )}
            </div>
          </div>
        </div>
      </div>
    </>
  );
}
Example #22
Source File: HistoryTable.tsx    From aqualink-app with MIT License 4 votes vote down vote up
HistoryTable = ({ site, uploadHistory, onDelete }: HistoryTableProps) => {
  const nUploads = uploadHistory.length;
  const classes = useStyles();
  const { timezone } = site;
  const timezoneAbbreviation = timezone
    ? moment().tz(timezone).zoneAbbr()
    : undefined;
  const dateFormat = "MM/DD/YYYY";

  const dataVisualizationButtonLink = (
    start: string,
    end: string,
    surveyPoint: number
  ) =>
    `/sites/${site.id}${requests.generateUrlQueryParams({
      start,
      end,
      surveyPoint,
    })}`;

  if (nUploads === 0) {
    return null;
  }

  return (
    <div className={classes.root}>
      <div>
        <Typography variant="h6" gutterBottom>
          {nUploads} {pluralize(nUploads, "file")} previously uploaded
        </Typography>
      </div>
      <TableContainer>
        <Table className={classes.table}>
          <TableHead>
            <TableRow>
              {tableHeaderTitles.map((title) => (
                <TableCell key={title} className={classes.headCell}>
                  <Typography {...tableCellTypographyProps}>{title}</Typography>
                </TableCell>
              ))}
            </TableRow>
          </TableHead>
          <TableBody>
            {uploadHistory.map(
              ({
                id,
                file,
                surveyPoint,
                sensorType,
                minDate,
                maxDate,
                createdAt,
              }) => {
                const row = [
                  file,
                  timezoneAbbreviation || timezone,
                  site.name,
                  surveyPoint.name,
                  startCase(sensorType),
                  moment(createdAt).format(dateFormat),
                ];
                return (
                  <TableRow key={id}>
                    {row.map((item) => (
                      <TableCell>
                        <Typography {...tableCellTypographyProps}>
                          {item}
                        </Typography>
                      </TableCell>
                    ))}
                    <TableCell>
                      <Button
                        component={Link}
                        to={dataVisualizationButtonLink(
                          minDate,
                          maxDate,
                          surveyPoint.id
                        )}
                        size="small"
                        variant="outlined"
                        color="primary"
                        className={classes.dateIntervalButton}
                      >
                        {moment(minDate).format(dateFormat)} -{" "}
                        {moment(maxDate).format(dateFormat)}
                      </Button>
                    </TableCell>
                    <TableCell>
                      <DeleteButton
                        onConfirm={() => onDelete([id])}
                        content={
                          <Typography color="textSecondary">
                            Are you sure you want to delete file &quot;
                            <span className={classes.bold}>{file}</span>&quot;?
                            Data between dates{" "}
                            <span className={classes.bold}>
                              {moment(minDate).format("MM/DD/YYYY HH:mm")}
                            </span>{" "}
                            and{" "}
                            <span className={classes.bold}>
                              {moment(maxDate).format("MM/DD/YYYY HH:mm")}
                            </span>{" "}
                            will be lost.
                          </Typography>
                        }
                      />
                    </TableCell>
                  </TableRow>
                );
              }
            )}
          </TableBody>
        </Table>
      </TableContainer>
    </div>
  );
}
Example #23
Source File: Summary.tsx    From UsTaxes with GNU Affero General Public License v3.0 4 votes vote down vote up
F1040Summary = ({ summary }: F1040SummaryProps): ReactElement => (
  <>
    {(() => {
      if (summary.amountOwed !== undefined && summary.amountOwed > 0) {
        return (
          <div>
            <Typography variant="body2" color="textSecondary">
              Amount Owed: <Currency value={-summary.amountOwed} />
            </Typography>
          </div>
        )
      }
      if (summary.refundAmount !== undefined && summary.refundAmount > 0) {
        return (
          <div>
            <Typography variant="body2" color="textSecondary">
              Refund Amount: <Currency value={summary.refundAmount} />
            </Typography>
          </div>
        )
      }
    })()}

    <h3>Credits</h3>
    <Grid container>
      <Grid item zeroMinWidth>
        <List>
          {summary.credits.map((credit) => (
            <BinaryStateListItem key={credit.name} active={credit.allowed}>
              <Typography variant="body2" color="textPrimary">
                {credit.name}
              </Typography>
              {(() => {
                if (credit.value !== undefined) {
                  return (
                    <Typography variant="body2" color="textSecondary">
                      Credit: <Currency value={credit.value} />
                    </Typography>
                  )
                }
                return <></>
              })()}
            </BinaryStateListItem>
          ))}
        </List>
      </Grid>
    </Grid>

    {(() => {
      if (summary.worksheets.length > 0) {
        return (
          <Grid container>
            <Grid item zeroMinWidth>
              <h3>Worksheets</h3>
              <List>
                {summary.worksheets.map((worksheet, idx) => (
                  <Accordion key={idx}>
                    <AccordionSummary expandIcon={<ExpandMore />}>
                      {worksheet.name}
                    </AccordionSummary>
                    <AccordionDetails>
                      <TableContainer>
                        <Table size="small">
                          <TableBody>
                            {worksheet.lines.map((line) => (
                              <TableRow key={line.line}>
                                <TableCell component="th">
                                  Line {line.line}
                                </TableCell>
                                <TableCell>
                                  {typeof line.value === 'number' ? (
                                    <Currency
                                      value={displayRound(line.value) ?? 0}
                                    />
                                  ) : (
                                    line.value
                                  )}
                                </TableCell>
                              </TableRow>
                            ))}
                          </TableBody>
                        </Table>
                      </TableContainer>
                    </AccordionDetails>
                  </Accordion>
                ))}
              </List>
            </Grid>
          </Grid>
        )
      }
      return <></>
    })()}
  </>
)
Example #24
Source File: TreasuryTable.tsx    From lobis-frontend with MIT License 4 votes vote down vote up
function TreasuryTable() {
    const isAppLoading = useSelector<IReduxState, boolean>(state => state.app.loading);
    const app = useSelector<IReduxState, IAppSlice>(state => state.app);
    const crvPerLobi = app.crvTreasuryBalance / (app.totalSupply - app.multisigLobiBalance);
    const crvUSDPerLobi = (app.crvTreasuryBalance * app.crvPrice) / (app.totalSupply - app.multisigLobiBalance);
    const fxsPerLobi = app.fraxTreasuryBalance / (app.totalSupply - app.multisigLobiBalance);
    const fxsUSDPerLobi = (app.fraxTreasuryBalance * app.fxsPrice) / (app.totalSupply - app.multisigLobiBalance);
    const tokePerLobi = app.tokeTreasuryBalance / (app.totalSupply - app.multisigLobiBalance);
    const tokeUSDPerLobi = (app.tokeTreasuryBalance * app.tokePrice) / (app.totalSupply - app.multisigLobiBalance);
    const sdtPerLobi = app.treasurySdtBalance / (app.totalSupply - app.multisigLobiBalance);
    const sdtUSDPerLobi = (app.treasurySdtBalance * app.sdtPrice) / (app.totalSupply - app.multisigLobiBalance);
    const anglePerLobi = app.angleTreasuryBalance / (app.totalSupply - app.multisigLobiBalance);
    const angleUSDPerLobi = (app.angleTreasuryBalance * app.anglePrice) / (app.totalSupply - app.multisigLobiBalance);
    const gOhmPerLobi = app.gOhmTreasuryBalance / (app.totalSupply - app.multisigLobiBalance);
    const gOhmUSDPerLobi = (app.gOhmTreasuryBalance * app.gOhmPrice) / (app.totalSupply - app.multisigLobiBalance);
    const totalUSDPerLobi = crvUSDPerLobi + fxsUSDPerLobi + tokeUSDPerLobi;

    return (
        <Grid container item>
            <TableContainer className="treasury-balance-view-card-table">
                <Table>
                    <TableHead>
                        <TableRow>
                            <TableCell align="left">
                                <p className="treasury-balance-view-card-table-title">Token</p>
                            </TableCell>
                            <TableCell align="center">
                                <p className="treasury-balance-view-card-table-title">Treasury</p>
                            </TableCell>
                            <TableCell align="center">
                                <p className="treasury-balance-view-card-table-title">Token Price (USD)</p>
                            </TableCell>
                            <TableCell align="center">
                                <p className="treasury-balance-view-card-table-title">Reserve Values (USD)</p>
                            </TableCell>
                            <TableCell align="center">
                                <p className="treasury-balance-view-card-table-title">Backing Per LOBI (votes)</p>
                            </TableCell>
                            <TableCell align="center">
                                <p className="treasury-balance-view-card-table-title">Backing Per LOBI (USD)</p>
                            </TableCell>
                            <TableCell align="right"></TableCell>
                        </TableRow>
                    </TableHead>
                    <TableBody>
                        <TableRow className="data-row">
                            <TableCell align="left" className="token-name-title">
                                CRV
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.crvTreasuryBalance)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(app.crvPrice)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.crvTreasuryBalance * app.crvPrice)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(crvPerLobi)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(crvUSDPerLobi)}`}
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell align="left" className="token-name-title">
                                FXS
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.fraxTreasuryBalance)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(app.fxsPrice)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.fraxTreasuryBalance * app.fxsPrice)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(fxsPerLobi)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(fxsUSDPerLobi)}`}
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell align="left" className="token-name-title">
                                TOKE
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.tokeTreasuryBalance)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(app.tokePrice)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.tokeTreasuryBalance * app.tokePrice)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(tokePerLobi)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(tokeUSDPerLobi)}`}
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell align="left" className="token-name-title">
                                SDT
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.treasurySdtBalance)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(app.sdtPrice)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.treasurySdtBalance * app.sdtPrice)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(sdtPerLobi)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(sdtUSDPerLobi)}`}
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell align="left" className="token-name-title">
                                ANGLE
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.angleTreasuryBalance)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(app.anglePrice)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.angleTreasuryBalance * app.anglePrice)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(anglePerLobi)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(angleUSDPerLobi)}`}
                            </TableCell>
                        </TableRow>
                        <TableRow>
                            <TableCell align="left" className="token-name-title">
                                gOHM
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.gOhmTreasuryBalance)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(app.gOhmPrice)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.gOhmTreasuryBalance * app.gOhmPrice)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(gOhmPerLobi)}`}
                            </TableCell>
                            <TableCell align="center" className="token-name-title">
                                {`${new Intl.NumberFormat("en-US", {
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(gOhmUSDPerLobi)}`}
                            </TableCell>
                        </TableRow>
                    </TableBody>
                    <TableFooter>
                        {" "}
                        <TableRow>
                            <TableCell align="left" className="treasury-balance-view-card-table-title-footer">
                                <p className="treasury-balance-view-card-table-title"></p>
                            </TableCell>
                            <TableCell align="center" className="treasury-balance-view-card-table-title-footer"></TableCell>
                            <TableCell align="center" className="treasury-balance-view-card-table-title-footer"></TableCell>
                            <TableCell align="center" className="treasury-balance-view-card-table-title-footer">
                                {`${new Intl.NumberFormat("en-US", {
                                    style: "currency",
                                    currency: "USD",
                                    maximumFractionDigits: 0,
                                    minimumFractionDigits: 0,
                                }).format(app.treasuryBalance)}`}
                            </TableCell>
                            <TableCell align="center" className="treasury-balance-view-card-table-title-footer"></TableCell>
                            <TableCell align="center" className="treasury-balance-view-card-table-title-footer">
                                {`${new Intl.NumberFormat("en-US", {
                                    style: "currency",
                                    currency: "USD",
                                    maximumFractionDigits: 2,
                                    minimumFractionDigits: 0,
                                }).format(totalUSDPerLobi)}`}
                            </TableCell>
                        </TableRow>
                    </TableFooter>
                </Table>
            </TableContainer>
        </Grid>
    );
}
Example #25
Source File: PriceCompare.tsx    From akashlytics with GNU General Public License v3.0 4 votes vote down vote up
PriceCompare: React.FunctionComponent<IPriceCompareProps> = ({}) => {
  const { data: dashboardData, status } = useDashboardData();
  const classes = useStyles();
  const [priceComparisons, setPriceComparisons] = useState(null);
  const marketData = dashboardData?.marketData;
  const intl = useIntl();

  useEffect(() => {
    async function getPriceCompare() {
      const res = await fetch("/data/price-comparisons.json");
      const data = await res.json();

      if (data) {
        setPriceComparisons(data);
      }
    }

    getPriceCompare();
  }, []);

  return (
    <div className={clsx(classes.root, "container")}>
      <Helmet title="Price comparison">
        <meta
          name="description"
          content="Compare Akash cost savings against the cloud giants like Amazon Web Services (aws), Google Cloud Platform (gcp) and Microsoft Azure."
        />
      </Helmet>
      <div className={clsx("row", classes.titleContainer)}>
        <div className="col-xs-12">
          <Typography variant="h3" className={classes.pageTitle}>
            Akash vs. Cloud giants
          </Typography>
          <Typography variant="h5">A simple price comparison</Typography>
          <Typography variant="caption">$USD price per month</Typography>
        </div>
      </div>

      <div className="row">
        <div className="col-xs-12">
          {!priceComparisons || !marketData ? (
            <Box textAlign="center">
              <CircularProgress size={80} />
            </Box>
          ) : (
            <TableContainer component={Paper}>
              <Table className={classes.table} aria-label="price comparisons">
                <TableHead className={classes.tableHeader}>
                  <TableRow>
                    <TableCell align="center" width="10%">
                      type
                    </TableCell>
                    {priceComparisons.providers.map((provider) => (
                      <TableCell key={provider.key} align="center">
                        {provider.title}
                      </TableCell>
                    ))}
                  </TableRow>
                </TableHead>
                <TableBody>
                  {priceComparisons.rows.map((row, rowIndex) => {
                    const akashCell = row.cells.filter((c) => c.provider === "akash")[0];
                    const akashPrice = akashCell.amount * 0.432 * marketData.price;

                    return (
                      <React.Fragment key={row.type}>
                        <TableRow>
                          <TableCell align="center" component="th" scope="row" className={classes.dataCell}>
                            {row.type}
                          </TableCell>
                          {row.cells.map((cell) => (
                            <ProviderCell key={`${cell.provider}_${cell.amount}_${cell.unit}`} cell={cell} marketData={marketData} />
                          ))}
                        </TableRow>

                        <TableRow className={classes.tableRow}>
                          <TableCell align="center" component="th" scope="row" className={classes.discountCell}></TableCell>
                          {row.cells.map((cell, i) => {
                            const isAkash = cell.provider === "akash";
                            const discount = +(akashPrice - cell.amount) / cell.amount;

                            return (
                              <TableCell
                                key={`discount_${rowIndex}_${i}_${cell.provider}_${cell.amount}_${cell.unit}`}
                                align="center"
                                className={classes.discountCell}
                              >
                                {!isAkash ? (
                                  <Chip
                                    className={clsx(classes.discountChip, {
                                      [classes.discountChipGreen]: discount < 0
                                    })}
                                    size="small"
                                    label={intl.formatNumber(discount, {
                                      style: "percent",
                                      maximumFractionDigits: 2
                                    })}
                                  />
                                ) : (
                                  <div className={classes.discountLabel}>Akash discount:</div>
                                )}
                              </TableCell>
                            );
                          })}
                        </TableRow>
                      </React.Fragment>
                    );
                  })}
                </TableBody>
              </Table>
            </TableContainer>
          )}
        </div>
      </div>

      <div className={clsx("row", classes.disclaimerRow)}>
        <div className="col-xs-12">
          <Typography variant="h4" className={classes.disclaimerTitle}>
            Disclaimer
          </Typography>

          <u className={classes.disclaimerList}>
            <li>These prices may vary. I strongly suggest that you do your own research as I might have miss-calculated some of the providers pricing.</li>
            <li>The specifications used for comparisons are mostly focused on CPU and RAM as storage is usually rather cheap.</li>
            <li>
              As of today, the minimum pricing for a lease on akash is 1uakt (.000001akt) per block at an average of 1 block per 6 second, which gives
              ~.423akt/month. To counter the rise of prices, Akash will introduce fractional pricing which will enable even lower prices. Please refer to this{" "}
              <a href="https://akash.network/blog/akash-mainnet-2-update-april-29-2021/" target="_blank" rel="noopener" className={classes.link}>
                article.
              </a>
            </li>
            <li>
              To calculate the pricing for Akash, I created a deployment with the given specifications and took the best available bid. This might change in the
              future.
            </li>
            <li>
              <a href="https://calculator.s3.amazonaws.com/index.html" target="_blank" rel="noopener" className={classes.link}>
                Amazon Web Service pricing calculator
              </a>
            </li>
            <li>
              <a href="https://cloud.google.com/products/calculator" target="_blank" rel="noopener" className={classes.link}>
                Google cloud platform pricing calculator
              </a>
            </li>
            <li>
              <a href="https://azure.microsoft.com/en-us/pricing/calculator/" target="_blank" rel="noopener" className={classes.link}>
                Microsoft Azure pricing calculator
              </a>
            </li>
          </u>
        </div>
      </div>
    </div>
  );
}
Example #26
Source File: MetricAssignmentResults.tsx    From abacus with GNU General Public License v2.0 4 votes vote down vote up
/**
 * Display results for a MetricAssignment
 */
export default function MetricAssignmentResults({
  strategy,
  metricAssignment,
  metric,
  analysesByStrategyDateAsc,
  experiment,
  recommendation,
  variationDiffKey,
}: {
  strategy: AnalysisStrategy
  metricAssignment: MetricAssignment
  metric: Metric
  analysesByStrategyDateAsc: Record<AnalysisStrategy, Analysis[]>
  experiment: ExperimentFull
  recommendation: Recommendations.Recommendation
  variationDiffKey: string
}): JSX.Element | null {
  const classes = useStyles()

  const [isShowObservedData, setIsShowObservedData] = useState<boolean>(false)
  const toggleIsShowObservedData = () => {
    setIsShowObservedData((isShowObservedData) => !isShowObservedData)
  }

  const isConversion = metric.parameterType === MetricParameterType.Conversion
  const estimateTransform: (estimate: number | null) => number | null = isConversion
    ? (estimate: number | null) => estimate && estimate * 100
    : identity
  const analyses = analysesByStrategyDateAsc[strategy]
  const latestAnalysis = _.last(analyses)
  const latestEstimates = latestAnalysis?.metricEstimates
  if (!latestAnalysis || !latestEstimates) {
    return <MissingAnalysisMessage />
  }

  const [_changeVariationId, baseVariationId] = variationDiffKey.split('_')

  const dates = analyses.map(({ analysisDatetime }) => analysisDatetime.toISOString())

  const plotlyDataVariationGraph: Array<Partial<PlotData>> = [
    ..._.flatMap(experiment.variations, (variation, index) => {
      return [
        {
          name: `${variation.name}: lower bound`,
          x: dates,
          y: analyses
            .map(
              ({ metricEstimates }) => metricEstimates && metricEstimates.variations[variation.variationId].bottom_95,
            )
            .map(estimateTransform),
          line: {
            color: Visualizations.variantColors[index],
          },
          mode: 'lines' as const,
          type: 'scatter' as const,
        },
        {
          name: `${variation.name}: upper bound`,
          x: dates,
          y: analyses
            .map(({ metricEstimates }) => metricEstimates && metricEstimates.variations[variation.variationId].top_95)
            .map(estimateTransform),
          line: {
            color: Visualizations.variantColors[index],
          },
          fill: 'tonexty' as const,
          fillcolor: Visualizations.variantColors[index],
          mode: 'lines' as const,
          type: 'scatter' as const,
        },
      ]
    }),
  ]

  const plotlyDataDifferenceGraph: Array<Partial<PlotData>> = [
    {
      name: `difference: 99% lower bound`,
      x: dates,
      y: analyses
        .map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].bottom_99)
        .map(estimateTransform),
      line: { width: 0 },
      marker: { color: '444' },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: `difference: 99% upper bound`,
      x: dates,
      y: analyses
        .map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].top_99)
        .map(estimateTransform),
      fill: 'tonexty',
      fillcolor: 'rgba(0,0,0,.2)',
      line: { width: 0 },
      marker: { color: '444' },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: `difference: 95% lower bound`,
      x: dates,
      y: analyses
        .map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].bottom_95)
        .map(estimateTransform),
      line: { width: 0 },
      marker: { color: '444' },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: `difference: 95% upper bound`,
      x: dates,
      y: analyses
        .map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].top_95)
        .map(estimateTransform),
      fill: 'tonexty',
      fillcolor: 'rgba(0,0,0,.2)',
      line: { width: 0 },
      marker: { color: '444' },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: `difference: 50% lower bound`,
      x: dates,
      y: analyses
        .map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].bottom_50)
        .map(estimateTransform),
      line: { width: 0 },
      marker: { color: '444' },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: `difference: 50% upper bound`,
      x: dates,
      y: analyses
        .map(({ metricEstimates }) => metricEstimates && metricEstimates.diffs[variationDiffKey].top_50)
        .map(estimateTransform),
      fill: 'tonexty',
      fillcolor: 'rgba(0,0,0,.2)',
      line: { width: 0 },
      marker: { color: '444' },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: 'ROPE: lower bound',
      x: dates,
      y: analyses.map((_) => -metricAssignment.minDifference).map(estimateTransform),
      line: {
        color: 'rgba(0,0,0,.4)',
        dash: 'dash',
      },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
    {
      name: 'ROPE: upper bound',
      x: dates,
      y: analyses.map((_) => metricAssignment.minDifference).map(estimateTransform),
      line: {
        color: 'rgba(0,0,0,.4)',
        dash: 'dash',
      },
      mode: 'lines' as const,
      type: 'scatter' as const,
    },
  ]

  return (
    <div className={clsx(classes.root, 'analysis-detail-panel')}>
      <Typography className={classes.dataTableHeader}>Summary</Typography>
      <TableContainer component={Paper}>
        <Table>
          <TableBody>
            <TableRow>
              <TableCell>
                <Typography variant='h5' gutterBottom className={classes.recommendation}>
                  <AnalysisDisplay {...{ experiment, analysis: recommendation }} />
                </Typography>
                {recommendation.decision === Recommendations.Decision.ManualAnalysisRequired && (
                  <Typography variant='body1' gutterBottom>
                    <strong> Different strategies are recommending conflicting variations! </strong>
                  </Typography>
                )}
                <Typography variant='body1'>
                  {getOverviewMessage(experiment, recommendation)}{' '}
                  <Link
                    href={`https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#reading-the-data`}
                    target='_blank'
                  >
                    Learn more
                  </Link>
                </Typography>
              </TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <Typography variant='body1' gutterBottom>
                  The absolute change in the {isConversion ? 'conversion rate' : 'ARPU'} of{' '}
                  <MetricValue
                    metricParameterType={metric.parameterType}
                    isDifference={true}
                    value={latestEstimates.diffs[variationDiffKey].bottom_95}
                    displayPositiveSign
                    displayUnit={false}
                  />{' '}
                  to{' '}
                  <MetricValue
                    metricParameterType={metric.parameterType}
                    isDifference={true}
                    value={latestEstimates.diffs[variationDiffKey].top_95}
                    displayPositiveSign
                  />{' '}
                  is {recommendation.statisticallySignificant ? '' : ' not '}
                  statistically different from zero because the interval
                  {recommendation.statisticallySignificant ? ' excludes ' : ' includes '}
                  zero.{' '}
                  {
                    explanationLine2[
                      recommendation.practicallySignificant as Recommendations.PracticalSignificanceStatus
                    ]
                  }
                  <MetricValue
                    metricParameterType={metric.parameterType}
                    isDifference={true}
                    value={-metricAssignment.minDifference}
                    displayPositiveSign
                    displayUnit={false}
                  />{' '}
                  to{' '}
                  <MetricValue
                    metricParameterType={metric.parameterType}
                    isDifference={true}
                    value={metricAssignment.minDifference}
                    displayPositiveSign
                  />
                  .
                </Typography>
                <strong>Last analyzed:</strong>{' '}
                <DatetimeText datetime={latestAnalysis.analysisDatetime} excludeTime={true} />.
              </TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <strong>Metric description:</strong> {metric.description}
              </TableCell>
            </TableRow>
          </TableBody>
        </Table>
      </TableContainer>
      <Typography className={classes.dataTableHeader}>Analysis</Typography>
      <TableContainer component={Paper}>
        <Table className={classes.coolTable}>
          <TableHead>
            <TableRow>
              <TableCell>Variant</TableCell>
              <TableCell align='right'>
                {metric.parameterType === MetricParameterType.Revenue
                  ? 'Average revenue per user (ARPU) interval'
                  : 'Conversion rate interval'}
              </TableCell>
              <TableCell align='right'>Absolute change</TableCell>
              <TableCell align='right'>Relative change (lift)</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {experiment.variations.map((variation) => (
              <React.Fragment key={variation.variationId}>
                <TableRow>
                  <TableCell
                    component='th'
                    scope='row'
                    variant='head'
                    valign='top'
                    className={clsx(classes.rowHeader, classes.headerCell, classes.credibleIntervalHeader)}
                  >
                    <span className={classes.monospace}>{variation.name}</span>
                  </TableCell>
                  <TableCell className={classes.monospace} align='right'>
                    <MetricValueInterval
                      intervalName={'the metric value'}
                      metricParameterType={metric.parameterType}
                      bottomValue={latestEstimates.variations[variation.variationId].bottom_95}
                      topValue={latestEstimates.variations[variation.variationId].top_95}
                      displayPositiveSign={false}
                    />
                  </TableCell>
                  <TableCell className={classes.monospace} align='right'>
                    {variation.isDefault ? (
                      'Baseline'
                    ) : (
                      <MetricValueInterval
                        intervalName={'the absolute change between variations'}
                        metricParameterType={metric.parameterType}
                        isDifference={true}
                        bottomValue={latestEstimates.diffs[`${variation.variationId}_${baseVariationId}`].bottom_95}
                        topValue={latestEstimates.diffs[`${variation.variationId}_${baseVariationId}`].top_95}
                      />
                    )}
                  </TableCell>
                  <TableCell className={classes.monospace} align='right'>
                    {variation.isDefault ? (
                      'Baseline'
                    ) : (
                      <MetricValueInterval
                        intervalName={'the relative change between variations'}
                        metricParameterType={MetricParameterType.Conversion}
                        bottomValue={Analyses.ratioToDifferenceRatio(
                          latestEstimates.ratios[`${variation.variationId}_${baseVariationId}`].bottom_95,
                        )}
                        topValue={Analyses.ratioToDifferenceRatio(
                          latestEstimates.ratios[`${variation.variationId}_${baseVariationId}`].top_95,
                        )}
                      />
                    )}
                  </TableCell>
                </TableRow>
              </React.Fragment>
            ))}
          </TableBody>
        </Table>
      </TableContainer>
      <Typography className={classes.analysisFinePrint}>
        95% Credible Intervals (CIs). <strong> Experimenter-set minimum practical difference: </strong>{' '}
        <MetricValue
          value={metricAssignment.minDifference}
          metricParameterType={metric.parameterType}
          isDifference={true}
        />
        .
      </Typography>
      {dates.length > 1 ? (
        <div className={classes.metricEstimatePlots}>
          <Plot
            layout={{
              ...Visualizations.plotlyLayoutDefault,
              title: isConversion
                ? `Conversion rate estimates by variation (%)`
                : `Revenue estimates by variation (USD)`,
            }}
            data={plotlyDataVariationGraph}
            className={classes.metricEstimatePlot}
          />
          <Plot
            layout={{
              ...Visualizations.plotlyLayoutDefault,
              title: isConversion
                ? `Conversion rate difference estimates (percentage points)`
                : `Revenue difference estimates (USD)`,
            }}
            data={plotlyDataDifferenceGraph}
            className={classes.metricEstimatePlot}
          />
        </div>
      ) : (
        <Typography variant='body1' className={classes.noPlotMessage}>
          Past values will be plotted once we have more than one day of results.
        </Typography>
      )}
      <Typography
        className={clsx(classes.dataTableHeader, classes.clickable)}
        onClick={toggleIsShowObservedData}
        role='button'
      >
        {isShowObservedData ? (
          <ExpandMore className={classes.expandCollapseIcon} />
        ) : (
          <ChevronRight className={classes.expandCollapseIcon} />
        )}
        &quot;Observed&quot; data
      </Typography>
      {isShowObservedData && (
        <>
          <TableContainer component={Paper}>
            <Table className={classes.coolTable}>
              <TableHead>
                <TableRow>
                  <TableCell>Variant</TableCell>
                  <TableCell align='right'>Users</TableCell>
                  <TableCell align='right'>
                    {metric.parameterType === MetricParameterType.Revenue ? 'Revenue' : 'Conversions'}
                  </TableCell>
                  <TableCell align='right'>
                    {metric.parameterType === MetricParameterType.Revenue
                      ? 'Average revenue per user (ARPU)'
                      : 'Conversion rate'}
                  </TableCell>
                </TableRow>
              </TableHead>
              <TableBody>
                {experiment.variations.map((variation) => (
                  <React.Fragment key={variation.variationId}>
                    <TableRow>
                      <TableCell
                        component='th'
                        scope='row'
                        variant='head'
                        valign='top'
                        className={clsx(classes.rowHeader, classes.headerCell, classes.credibleIntervalHeader)}
                      >
                        <span className={classes.monospace}>{variation.name}</span>
                      </TableCell>
                      <TableCell className={classes.monospace} align='right'>
                        {latestAnalysis.participantStats[`variation_${variation.variationId}`].toLocaleString()}
                      </TableCell>
                      <TableCell className={classes.monospace} align='right'>
                        <MetricValue
                          value={
                            latestAnalysis.participantStats[`variation_${variation.variationId}`] *
                            latestEstimates.variations[variation.variationId].mean
                          }
                          metricParameterType={
                            metric.parameterType === MetricParameterType.Conversion
                              ? MetricParameterType.Count
                              : metric.parameterType
                          }
                        />
                      </TableCell>
                      <TableCell className={classes.monospace} align='right'>
                        <MetricValue
                          value={latestEstimates.variations[variation.variationId].mean}
                          metricParameterType={metric.parameterType}
                        />
                      </TableCell>
                    </TableRow>
                  </React.Fragment>
                ))}
              </TableBody>
            </Table>
          </TableContainer>
          <Typography variant='caption' gutterBottom>
            <Link href='https://wp.me/PCYsg-Fqg/#observed-data-uses-posterior-means' target='_blank'>
              &quot;Observed&quot; data as produced from our model, not raw observed data.
            </Link>{' '}
            For illustrative purposes only.
          </Typography>
        </>
      )}
    </div>
  )
}
Example #27
Source File: Audience.tsx    From abacus with GNU General Public License v2.0 4 votes vote down vote up
Audience = ({
  indexedSegments,
  formikProps,
  completionBag,
}: {
  indexedSegments: Record<number, Segment>
  formikProps: FormikProps<{ experiment: ExperimentFormData }>
  completionBag: ExperimentFormCompletionBag
}): JSX.Element => {
  const classes = useStyles()

  // The segmentExclusion code is currently split between here and SegmentAutocomplete
  // An improvement might be to have SegmentAutocomplete only handle Segment[] and for code here
  // to translate Segment <-> SegmentAssignment
  const [segmentAssignmentsField, _segmentAssignmentsFieldMeta, segmentAssignmentsFieldHelper] = useField(
    'experiment.segmentAssignments',
  )
  const [segmentExclusionState, setSegmentExclusionState] = useState<SegmentExclusionState>(() => {
    // We initialize the segmentExclusionState from existing data if there is any
    const firstSegmentAssignment = (segmentAssignmentsField.value as SegmentAssignmentNew[])[0]
    return firstSegmentAssignment && firstSegmentAssignment.isExcluded
      ? SegmentExclusionState.Exclude
      : SegmentExclusionState.Include
  })
  const onChangeSegmentExclusionState = (event: React.SyntheticEvent<HTMLInputElement>, value: string) => {
    setSegmentExclusionState(value as SegmentExclusionState)
    segmentAssignmentsFieldHelper.setValue(
      (segmentAssignmentsField.value as SegmentAssignmentNew[]).map((segmentAssignment: SegmentAssignmentNew) => {
        return {
          ...segmentAssignment,
          isExcluded: value === SegmentExclusionState.Exclude,
        }
      }),
    )
  }

  const platformError = formikProps.touched.experiment?.platform && formikProps.errors.experiment?.platform

  const variationsError =
    formikProps.touched.experiment?.variations && _.isString(formikProps.errors.experiment?.variations)
      ? formikProps.errors.experiment?.variations
      : undefined

  return (
    <div className={classes.root}>
      <Typography variant='h4' gutterBottom>
        Define Your Audience
      </Typography>

      <div className={classes.row}>
        <FormControl component='fieldset'>
          <FormLabel required>Platform</FormLabel>
          <Field component={Select} name='experiment.platform' displayEmpty error={!!platformError}>
            <MenuItem value='' disabled>
              Select a Platform
            </MenuItem>
            {Object.values(Platform).map((platform) => (
              <MenuItem key={platform} value={platform}>
                {platform}: {PlatformToHuman[platform]}
              </MenuItem>
            ))}
          </Field>
          <FormHelperText error={!!platformError}>
            {_.isString(platformError) ? platformError : undefined}
          </FormHelperText>
        </FormControl>
      </div>

      <div className={classes.row}>
        <FormControl component='fieldset'>
          <FormLabel required>User type</FormLabel>
          <FormHelperText>Types of users to include in experiment</FormHelperText>

          <Field component={FormikMuiRadioGroup} name='experiment.existingUsersAllowed' required>
            <FormControlLabel
              value='true'
              label='All users (new + existing + anonymous)'
              control={<Radio disabled={formikProps.isSubmitting} />}
              disabled={formikProps.isSubmitting}
            />
            <FormControlLabel
              value='false'
              label='Filter for newly signed up users (they must be also logged in)'
              control={<Radio disabled={formikProps.isSubmitting} />}
              disabled={formikProps.isSubmitting}
            />
          </Field>
        </FormControl>
      </div>
      <div className={classes.row}>
        <FormControl component='fieldset' className={classes.segmentationFieldSet}>
          <FormLabel htmlFor='segments-select'>Targeting</FormLabel>
          <FormHelperText className={classes.segmentationHelperText}>
            Who should see this experiment? <br /> Add optional filters to include or exclude specific target audience
            segments.
          </FormHelperText>
          <MuiRadioGroup
            aria-label='include-or-exclude-segments'
            className={classes.segmentationExclusionState}
            value={segmentExclusionState}
            onChange={onChangeSegmentExclusionState}
          >
            <FormControlLabel
              value={SegmentExclusionState.Include}
              control={<Radio />}
              label='Include'
              name='non-formik-segment-exclusion-state-include'
            />
            <FormControlLabel
              value={SegmentExclusionState.Exclude}
              control={<Radio />}
              label='Exclude'
              name='non-formik-segment-exclusion-state-exclude'
            />
          </MuiRadioGroup>
          <Field
            name='experiment.segmentAssignments'
            component={SegmentsAutocomplete}
            options={Object.values(indexedSegments)}
            // TODO: Error state, see https://stackworx.github.io/formik-material-ui/docs/api/material-ui-lab
            renderInput={(params: AutocompleteRenderInputParams) => (
              /* eslint-disable @typescript-eslint/no-unsafe-member-access */
              <MuiTextField
                {...params}
                variant='outlined'
                placeholder={segmentAssignmentsField.value.length === 0 ? 'Search and select to customize' : undefined}
              />
              /* eslint-enable @typescript-eslint/no-unsafe-member-access */
            )}
            segmentExclusionState={segmentExclusionState}
            indexedSegments={indexedSegments}
            fullWidth
            id='segments-select'
          />
        </FormControl>
      </div>
      <div className={classes.row}>
        <FormControl component='fieldset' className={classes.segmentationFieldSet}>
          <FormLabel htmlFor='variations-select'>Variations</FormLabel>
          <FormHelperText className={classes.segmentationHelperText}>
            Set the percentage of traffic allocated to each variation. Percentages may sum to less than 100 to avoid
            allocating the entire userbase. <br /> Use &ldquo;control&rdquo; for the default (fallback) experience.
          </FormHelperText>
          {variationsError && <FormHelperText error>{variationsError}</FormHelperText>}
          <TableContainer>
            <Table className={classes.variants}>
              <TableHead>
                <TableRow>
                  <TableCell> Name </TableCell>
                  <TableCell> Allocated Percentage </TableCell>
                  <TableCell></TableCell>
                </TableRow>
              </TableHead>
              <TableBody>
                <FieldArray
                  name={`experiment.variations`}
                  render={(arrayHelpers) => {
                    const onAddVariation = () => {
                      arrayHelpers.push({
                        name: ``,
                        isDefault: false,
                        allocatedPercentage: '',
                      })
                    }

                    const onRemoveVariation = (index: number) => arrayHelpers.remove(index)

                    const variations = formikProps.values.experiment.variations

                    return (
                      <>
                        {variations.map((variation, index) => {
                          return (
                            // The key here needs to be changed for variable variations
                            <TableRow key={index}>
                              <TableCell>
                                {variation.isDefault ? (
                                  variation.name
                                ) : (
                                  <Field
                                    component={FormikMuiTextField}
                                    name={`experiment.variations[${index}].name`}
                                    size='small'
                                    variant='outlined'
                                    required
                                    inputProps={{
                                      'aria-label': 'Variation Name',
                                    }}
                                  />
                                )}
                              </TableCell>
                              <TableCell>
                                <Field
                                  className={classes.variationAllocatedPercentage}
                                  component={FormikMuiTextField}
                                  name={`experiment.variations[${index}].allocatedPercentage`}
                                  type='number'
                                  size='small'
                                  variant='outlined'
                                  inputProps={{ min: 1, max: 99, 'aria-label': 'Allocated Percentage' }}
                                  required
                                  InputProps={{
                                    endAdornment: <InputAdornment position='end'>%</InputAdornment>,
                                  }}
                                />
                              </TableCell>
                              <TableCell>
                                {!variation.isDefault && 2 < variations.length && (
                                  <IconButton onClick={() => onRemoveVariation(index)} aria-label='Remove variation'>
                                    <Clear />
                                  </IconButton>
                                )}
                              </TableCell>
                            </TableRow>
                          )
                        })}
                        <TableRow>
                          <TableCell colSpan={3}>
                            <Alert severity='warning' className={classes.abnWarning}>
                              <strong> Manual analysis only A/B/n </strong>
                              <br />
                              <p>
                                Experiments with more than a single treatment variation are in an early alpha stage.
                              </p>
                              <p>No results will be displayed.</p>
                              <p>
                                Please do not set up such experiments in production without consulting the ExPlat team
                                first.
                              </p>

                              <div className={classes.addVariation}>
                                <Add className={classes.addVariationIcon} />
                                <Button
                                  variant='contained'
                                  onClick={onAddVariation}
                                  disableElevation
                                  size='small'
                                  aria-label='Add Variation'
                                >
                                  Add Variation
                                </Button>
                              </div>
                            </Alert>
                          </TableCell>
                        </TableRow>
                      </>
                    )
                  }}
                />
              </TableBody>
            </Table>
          </TableContainer>
        </FormControl>
      </div>
      {isDebugMode() && (
        <div className={classes.row}>
          <FormControl component='fieldset'>
            <FormLabel htmlFor='experiment.exclusionGroupTagIds'>Exclusion Groups</FormLabel>
            <FormHelperText>Optionally add this experiment to a mutually exclusive experiment group.</FormHelperText>
            <br />
            <Field
              component={AbacusAutocomplete}
              name='experiment.exclusionGroupTagIds'
              id='experiment.exclusionGroupTagIds'
              fullWidth
              options={
                // istanbul ignore next; trivial
                completionBag.exclusionGroupCompletionDataSource.data ?? []
              }
              loading={completionBag.exclusionGroupCompletionDataSource.isLoading}
              multiple
              renderOption={(option: AutocompleteItem) => <Chip label={option.name} />}
              renderInput={(params: AutocompleteRenderInputParams) => (
                <MuiTextField
                  {...params}
                  variant='outlined'
                  InputProps={{
                    ...autocompleteInputProps(params, completionBag.exclusionGroupCompletionDataSource.isLoading),
                  }}
                  InputLabelProps={{
                    shrink: true,
                  }}
                />
              )}
            />
          </FormControl>
        </div>
      )}
    </div>
  )
}
Example #28
Source File: Metrics.tsx    From abacus with GNU General Public License v2.0 4 votes vote down vote up
Metrics = ({
  indexedMetrics,
  completionBag,
  formikProps,
}: {
  indexedMetrics: Record<number, Metric>
  completionBag: ExperimentFormCompletionBag
  formikProps: FormikProps<{ experiment: ExperimentFormData }>
}): JSX.Element => {
  const classes = useStyles()
  const metricEditorClasses = useMetricEditorStyles()
  const decorationClasses = useDecorationStyles()

  // Metric Assignments
  const [metricAssignmentsField, _metricAssignmentsFieldMetaProps, metricAssignmentsFieldHelperProps] =
    useField<MetricAssignment[]>('experiment.metricAssignments')
  const [selectedMetric, setSelectedMetric] = useState<Metric | null>(null)
  const onChangeSelectedMetricOption = (_event: unknown, value: Metric | null) => setSelectedMetric(value)

  const makeMetricAssignmentPrimary = (indexToSet: number) => {
    metricAssignmentsFieldHelperProps.setValue(
      metricAssignmentsField.value.map((metricAssignment, index) => ({
        ...metricAssignment,
        isPrimary: index === indexToSet,
      })),
    )
  }

  // This picks up the no metric assignments validation error
  const metricAssignmentsError =
    formikProps.touched.experiment?.metricAssignments &&
    _.isString(formikProps.errors.experiment?.metricAssignments) &&
    formikProps.errors.experiment?.metricAssignments

  // ### Exposure Events
  const [exposureEventsField, _exposureEventsFieldMetaProps, _exposureEventsFieldHelperProps] =
    useField<EventNew[]>('experiment.exposureEvents')

  return (
    <div className={classes.root}>
      <Typography variant='h4' gutterBottom>
        Assign Metrics
      </Typography>

      <FieldArray
        name='experiment.metricAssignments'
        render={(arrayHelpers) => {
          const onAddMetric = () => {
            if (selectedMetric) {
              const metricAssignment = createMetricAssignment(selectedMetric)
              arrayHelpers.push({
                ...metricAssignment,
                isPrimary: metricAssignmentsField.value.length === 0,
              })
            }
            setSelectedMetric(null)
          }

          return (
            <>
              <TableContainer>
                <Table>
                  <TableHead>
                    <TableRow>
                      <TableCell>Metric</TableCell>
                      <TableCell>Attribution Window</TableCell>
                      <TableCell>Change Expected?</TableCell>
                      <TableCell>Minimum Practical Difference</TableCell>
                      <TableCell />
                    </TableRow>
                  </TableHead>
                  <TableBody>
                    {metricAssignmentsField.value.map((metricAssignment, index) => {
                      const onRemoveMetricAssignment = () => {
                        arrayHelpers.remove(index)
                      }

                      const onMakePrimary = () => {
                        makeMetricAssignmentPrimary(index)
                      }

                      const attributionWindowError =
                        (_.get(
                          formikProps.touched,
                          `experiment.metricAssignments[${index}].attributionWindowSeconds`,
                        ) as boolean | undefined) &&
                        (_.get(
                          formikProps.errors,
                          `experiment.metricAssignments[${index}].attributionWindowSeconds`,
                        ) as string | undefined)

                      return (
                        <TableRow key={index}>
                          <TableCell className={classes.metricNameCell}>
                            <Tooltip arrow title={indexedMetrics[metricAssignment.metricId].description}>
                              <span className={clsx(classes.metricName, decorationClasses.tooltipped)}>
                                {indexedMetrics[metricAssignment.metricId].name}
                              </span>
                            </Tooltip>
                            <br />
                            {metricAssignment.isPrimary && <Attribute name='primary' className={classes.monospaced} />}
                          </TableCell>
                          <TableCell>
                            <Field
                              className={classes.attributionWindowSelect}
                              component={Select}
                              name={`experiment.metricAssignments[${index}].attributionWindowSeconds`}
                              labelId={`experiment.metricAssignments[${index}].attributionWindowSeconds`}
                              size='small'
                              variant='outlined'
                              autoWidth
                              displayEmpty
                              error={!!attributionWindowError}
                              SelectDisplayProps={{
                                'aria-label': 'Attribution Window',
                              }}
                            >
                              <MenuItem value=''>-</MenuItem>
                              {Object.entries(AttributionWindowSecondsToHuman).map(
                                ([attributionWindowSeconds, attributionWindowSecondsHuman]) => (
                                  <MenuItem value={attributionWindowSeconds} key={attributionWindowSeconds}>
                                    {attributionWindowSecondsHuman}
                                  </MenuItem>
                                ),
                              )}
                            </Field>
                            {_.isString(attributionWindowError) && (
                              <FormHelperText error>{attributionWindowError}</FormHelperText>
                            )}
                          </TableCell>
                          <TableCell className={classes.changeExpected}>
                            <Field
                              component={Switch}
                              name={`experiment.metricAssignments[${index}].changeExpected`}
                              id={`experiment.metricAssignments[${index}].changeExpected`}
                              type='checkbox'
                              aria-label='Change Expected'
                              variant='outlined'
                            />
                          </TableCell>
                          <TableCell>
                            <MetricDifferenceField
                              className={classes.minDifferenceField}
                              name={`experiment.metricAssignments[${index}].minDifference`}
                              id={`experiment.metricAssignments[${index}].minDifference`}
                              metricParameterType={indexedMetrics[metricAssignment.metricId].parameterType}
                            />
                          </TableCell>
                          <TableCell>
                            <MoreMenu>
                              <MenuItem onClick={onMakePrimary}>Set as Primary</MenuItem>
                              <MenuItem onClick={onRemoveMetricAssignment}>Remove</MenuItem>
                            </MoreMenu>
                          </TableCell>
                        </TableRow>
                      )
                    })}
                    {metricAssignmentsField.value.length === 0 && (
                      <TableRow>
                        <TableCell colSpan={5}>
                          <Typography variant='body1' align='center'>
                            You don&apos;t have any metric assignments yet.
                          </Typography>
                        </TableCell>
                      </TableRow>
                    )}
                  </TableBody>
                </Table>
              </TableContainer>
              <div className={metricEditorClasses.addMetric}>
                <Add className={metricEditorClasses.addMetricAddSymbol} />
                <FormControl className={classes.addMetricSelect}>
                  <MetricAutocomplete
                    id='add-metric-select'
                    value={selectedMetric}
                    onChange={onChangeSelectedMetricOption}
                    options={Object.values(indexedMetrics)}
                    error={metricAssignmentsError}
                    fullWidth
                  />
                </FormControl>
                <Button variant='contained' disableElevation size='small' onClick={onAddMetric} aria-label='Add metric'>
                  Assign
                </Button>
              </div>
            </>
          )
        }}
      />

      <Alert severity='info' className={classes.metricsInfo}>
        <Link
          underline='always'
          href="https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#how-do-i-choose-a-primary-metric"
          target='_blank'
        >
          How do I choose a Primary Metric?
        </Link>
        &nbsp;
        <Link
          underline='always'
          href="https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#what-does-change-expected-mean-for-a-metric"
          target='_blank'
        >
          What is Change Expected?
        </Link>
      </Alert>

      <CollapsibleAlert
        id='attr-window-panel'
        severity='info'
        className={classes.attributionWindowInfo}
        summary={'What is an Attribution Window?'}
      >
        <Link
          underline='always'
          href="https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#what-is-an-attribution-window-for-a-metric"
          target='_blank'
        >
          An Attribution Window
        </Link>{' '}
        is the window of time after exposure to an experiment that we capture metric events for a participant (exposure
        can be from either assignment or specified exposure events). The refund window is the window of time after a
        purchase event. Revenue metrics will automatically deduct transactions that have been refunded within the
        metric’s refund window.
        <br />
        <div className={classes.attributionWindowDiagram}>
          <AttributionWindowDiagram />
          <RefundWindowDiagram />
        </div>
      </CollapsibleAlert>

      <CollapsibleAlert
        id='min-diff-panel'
        severity='info'
        className={classes.minDiffInfo}
        summary={'How do I choose a Minimum Difference?'}
      >
        <Link
          underline='always'
          href="https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#how-do-i-choose-a-minimum-difference-practically-equivalent-value-for-my-metrics"
          target='_blank'
        >
          Minimum Practical Difference values
        </Link>{' '}
        are absolute differences from the baseline (not relative). For example, if the baseline conversion rate is 5%, a
        minimum difference of 0.5 pp is equivalent to a 10% relative change.
        <br />
        <div className={classes.minDiffDiagram}>
          <MinDiffDiagram />
        </div>
      </CollapsibleAlert>

      <Alert severity='info' className={classes.requestMetricInfo}>
        <Link underline='always' href='https://betterexperiments.wordpress.com/?start=metric-request' target='_blank'>
          {"Can't find a metric? Request one!"}
        </Link>
      </Alert>

      <Typography variant='h4' className={classes.exposureEventsTitle}>
        Exposure Events
      </Typography>

      <FieldArray
        name='experiment.exposureEvents'
        render={(arrayHelpers) => {
          const onAddExposureEvent = () => {
            arrayHelpers.push({
              event: '',
              props: [],
            })
          }
          return (
            <>
              <TableContainer>
                <Table>
                  <TableBody>
                    {exposureEventsField.value.map((exposureEvent, index) => (
                      <EventEditor
                        key={index}
                        {...{ arrayHelpers, index, classes, completionBag, exposureEvent }}
                        onRemoveExposureEvent={() => arrayHelpers.remove(index)}
                      />
                    ))}
                    {exposureEventsField.value.length === 0 && (
                      <TableRow>
                        <TableCell colSpan={1}>
                          <Typography variant='body1' align='center'>
                            You don&apos;t have any exposure events.
                            {}
                            <br />
                            {}
                            We strongly suggest considering adding one to improve the accuracy of your metrics.
                          </Typography>
                        </TableCell>
                      </TableRow>
                    )}
                  </TableBody>
                </Table>
              </TableContainer>
              <div className={metricEditorClasses.addMetric}>
                <Add className={metricEditorClasses.addMetricAddSymbol} />
                <Button
                  variant='contained'
                  disableElevation
                  size='small'
                  onClick={onAddExposureEvent}
                  aria-label='Add exposure event'
                >
                  Add Event
                </Button>
              </div>
            </>
          )
        }}
      />

      <Alert severity='info' className={classes.exposureEventsInfo}>
        <Link
          underline='always'
          href="https://github.com/Automattic/experimentation-platform/wiki/Experimenter's-Guide#what-is-an-exposure-event-and-when-do-i-need-it"
          target='_blank'
        >
          What is an Exposure Event? And when do I need it?
        </Link>
        <br />
        <span>Only validated events can be used as exposure events.</span>
      </Alert>

      <Alert severity='info' className={classes.multipleExposureEventsInfo}>
        If you have multiple exposure events, then participants will be considered exposed if they trigger{' '}
        <strong>any</strong> of the exposure events.
      </Alert>
    </div>
  )
}
Example #29
Source File: App.tsx    From react-final-table with MIT License 4 votes vote down vote up
function App() {
  const [searchString, setSearchString] = useState('');

  const {
    headers,
    rows,
    selectRow,
    selectedRows,
    originalRows,
    toggleSort,
    toggleAll,
  } = useTable<DataType>(columns, data, {
    selectable: true,
    filter: useCallback(
      (rows: RowType<DataType>[]) => {
        return rows.filter(row => {
          return (
            row.cells.filter(cell => {
              if (cell.value.toLowerCase().includes(searchString)) {
                return true;
              }
              return false;
            }).length > 0
          );
        });
      },
      [searchString]
    ),
  });

  return (
    <Grid container>
      <Grid item>
        <TableContainer>
          <Table>
            <TableHead>
              <TableRow>
                <TableCell>
                  <Checkbox
                    indeterminate={
                      selectedRows.length > 0 &&
                      selectedRows.length !== rows.length
                    }
                    checked={selectedRows.length === rows.length}
                    onClick={() => toggleAll()}
                  />
                </TableCell>
                {headers.map(column => (
                  <TableCell onClick={() => toggleSort(column.name)}>
                    {column.render()}{' '}
                    {column.sorted.on ? (
                      <>
                        {column.sorted.asc ? (
                          <ArrowUpward />
                        ) : (
                          <ArrowDownward />
                        )}
                      </>
                    ) : null}
                  </TableCell>
                ))}
              </TableRow>
            </TableHead>
            <TableBody>
              {rows.map(row => (
                <TableRow>
                  <TableCell>
                    <Checkbox
                      checked={row.selected}
                      onChange={() => selectRow(row.id)}
                    />
                  </TableCell>
                  {row.cells.map(cell => (
                    <TableCell>{cell.render()}</TableCell>
                  ))}
                </TableRow>
              ))}
            </TableBody>
          </Table>
        </TableContainer>
        <TableContainer>
          <TableHead>
            <TableRow>
              {headers.map(column => (
                <TableCell>{column.label}</TableCell>
              ))}
            </TableRow>
          </TableHead>
          <TableBody>
            {selectedRows.map(row => {
              return (
                <TableRow>
                  <TableCell>
                    <Button onClick={() => selectRow(row.id)}>
                      Deselect Row
                    </Button>
                  </TableCell>
                  {row.cells.map(cell => {
                    return <TableCell>{cell.render()}</TableCell>;
                  })}
                </TableRow>
              );
            })}
          </TableBody>
        </TableContainer>
        <TextField
          variant="outlined"
          label="Search..."
          value={searchString}
          onChange={e => setSearchString(e.target.value)}
        />
        <pre>
          <code>
            {JSON.stringify({ selectedRows, originalRows, rows }, null, 2)}
          </code>
        </pre>
      </Grid>
    </Grid>
  );
}