@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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
FlexChild = styled(TableContainer)`
flex: 0 0 auto;
`
Example #12
Source File: MetricsTable.tsx From abacus with GNU General Public License v2.0 | 5 votes |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 "
<span className={classes.bold}>{file}</span>"?
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 |
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 |
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 |
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 |
/**
* 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} />
)}
"Observed" 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'>
"Observed" 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 |
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 “control” 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 |
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'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>
<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'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 |
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>
);
}