Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions backend/frontend/src/components/PixelDiffNumber.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Box, Text } from '@mantine/core';

export function PixelDiffNumber({ value }: { value: number }) {
const style = diffStyle(value);

function diffDisplay(diff: number): string {
return `${Math.round(diff * 100000) / 1000}%`;
}

function diffStyle(diff: number): { backgroundColor: string; color: string } {
if (diff === 0.0) return { backgroundColor: '#eeeeee', color: '#111111' };
else if (diff < 0.001) return { backgroundColor: '#4ce600', color: '#111111' };
else if (diff < 0.01) return { backgroundColor: '#55cc00', color: '#111111' };
else if (diff < 0.05) return { backgroundColor: '#66cc00', color: '#111111' };
else if (diff < 0.1) return { backgroundColor: '#ede621', color: '#111111' };
else if (diff < 0.25) return { backgroundColor: '#edc421', color: '#111111' };
else if (diff < 0.5) return { backgroundColor: '#cc5000', color: '#ffffff' };
else if (diff < 0.75) return { backgroundColor: '#cc4400', color: '#ffffff' };
else if (diff < 1.0) return { backgroundColor: '#cc2200', color: '#ffffff' };
else return { backgroundColor: '#cc0000', color: '#ffffff' };
}

return (
<Box px={4} py={2} style={{ borderRadius: 4, display: 'inline-block', ...style }}>
<Text size={'sm'}>{diffDisplay(value)}</Text>
</Box>
);
}
21 changes: 14 additions & 7 deletions backend/frontend/src/components/SortableHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Table } from '@mantine/core';
import { Stack, Table, UnstyledButton } from '@mantine/core';

import { SortColumn, SortDirection } from '../types';

interface Props {
sortKey: SortColumn;
label: string;
onSort: (col: SortColumn) => void;
filter?: React.ReactNode;
activeColumn?: SortColumn;
direction?: SortDirection;
}
Expand All @@ -14,18 +15,24 @@ export function SortableHeader({
sortKey,
label,
onSort,
filter,
activeColumn,
direction
}: Props) {
const isActive = activeColumn === sortKey;
const indicator = isActive ? (direction === 'asc' ? ' ↑' : ' ↓') : '';
return (
<Table.Th
style={{ cursor: 'pointer', whiteSpace: 'nowrap' }}
onClick={() => onSort(sortKey)}
>
{label}
{indicator}
<Table.Th>
<Stack gap={0}>
<UnstyledButton
style={{ cursor: 'pointer', whiteSpace: 'nowrap' }}
onClick={() => onSort(sortKey)}
>
{label}
{indicator}
</UnstyledButton>
{filter}
</Stack>
</Table.Th>
);
}
277 changes: 172 additions & 105 deletions backend/frontend/src/components/TestHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,125 +1,192 @@
import { Anchor, Box, Button, Table, Text } from '@mantine/core';
import { Anchor, Box, Button, Group, Modal, Stack, Table, Text } from '@mantine/core';

import { TestData, TestRecord } from '../types';
import { diffDisplay, diffStyle, timingDisplay } from '../utils';
import { timingDisplay } from '../utils';

import { ImageThumbnail } from './ImageThumbnail';
import { useState } from 'react';
import { useDisclosure } from '@mantine/hooks';
import { PixelDiffNumber } from './PixelDiffNumber';

interface Props {
record: TestRecord;
onUpdateReference: (record: TestRecord) => void;
}

export function TestHistory({ record, onUpdateReference }: Props) {
const [wasUpdated, setWasUpdated] = useState(false);
const [confirmOpened, { open, close }] = useDisclosure(false);

const testData = [...record.data].reverse();

const ImageWidth = 250;

return (
<Box p={'md'} bg={'dark.8'}>
<Table
horizontalSpacing={'xs'}
verticalSpacing={4}
withTableBorder
withColumnBorders
<>
<Modal
opened={confirmOpened}
onClose={close}
title="Are you sure?"
size="lg"
withCloseButton
centered
>
<Table.Thead>
<Table.Tr>
<Table.Th>Timestamp</Table.Th>
<Table.Th>Error</Table.Th>
<Table.Th>Commit</Table.Th>
<Table.Th>Timing</Table.Th>
<Table.Th>Log</Table.Th>
<Table.Th>Candidate</Table.Th>
<Table.Th>Reference</Table.Th>
<Table.Th>Difference</Table.Th>
<Table.Th />
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{testData.map((d: TestData, i: number) => (
<Table.Tr key={d.timeStamp}>
<Table.Td>
<Text size={'sm'} c={'dimmed'}>
{new Date(d.timeStamp).toISOString().split('T')[0]}
<br />
{new Date(d.timeStamp).toISOString().split('T')[1]?.replace('Z', '')}
</Text>
</Table.Td>
<Table.Td>
<Box
px={4}
style={{
borderRadius: 4,
display: 'inline-block',
width: 70,
textAlign: 'center',
...diffStyle(d.pixelError)
}}
>
<Text>{diffDisplay(d.pixelError)}</Text>
</Box>
</Table.Td>
<Table.Td>
<Anchor
href={`https://github.com/OpenSpace/OpenSpace/commit/${d.commitHash}`}
target={'_blank'}
>
{d.commitHash.substring(0, 8)}
</Anchor>
</Table.Td>
<Table.Td>
<Text>{timingDisplay(d.timing)}</Text>
</Table.Td>
<Table.Td>
<Anchor
href={`/api/result/log/${record.group}/${record.name}/${record.hardware}/${d.timeStamp}`}
target={'_blank'}
>
Log ({d.nErrors} errors)
</Anchor>
</Table.Td>
<Table.Td>
<ImageThumbnail
type={'candidate'}
group={record.group}
name={record.name}
hardware={record.hardware}
timestamp={d.timeStamp}
width={ImageWidth}
/>
</Table.Td>
<Table.Td>
<ImageThumbnail
type={'reference'}
group={record.group}
name={record.name}
hardware={record.hardware}
timestamp={d.timeStamp}
width={ImageWidth}
/>
</Table.Td>
<Table.Td>
<ImageThumbnail
type={'difference'}
group={record.group}
name={record.name}
hardware={record.hardware}
timestamp={d.timeStamp}
width={ImageWidth}
/>
</Table.Td>
<Table.Td>
{i === 0 && (
<Button variant={'default'} onClick={() => onUpdateReference(record)}>
Upgrade Candidate to Reference
</Button>
)}
</Table.Td>
<Stack align="center">
<Text px={'md'}>
Are you sure you want to update the reference image to the candidate for the
test: {record.name}?{' '}
<Text span fw={'bold'}>
This action cannot be undone.
</Text>
</Text>

<Group align={'center'} my={'sm'} wrap="nowrap">
<Stack align={'center'} gap={'xs'}>
<Text>Candidate</Text>
<ImageThumbnail
type={'candidate'}
group={record.group}
name={record.name}
hardware={record.hardware}
timestamp={testData[0].timeStamp}
width={ImageWidth}
/>
</Stack>
<Text>→</Text>
<Stack align={'center'} gap={'xs'}>
<Text>Reference</Text>
<ImageThumbnail
type={'reference'}
group={record.group}
name={record.name}
hardware={record.hardware}
timestamp={testData[0].timeStamp}
width={ImageWidth}
/>
</Stack>
</Group>
<Text>
Error: <PixelDiffNumber value={testData[0].pixelError} />
</Text>

<Text c={'dimmed'} size={'sm'} px={'md'}>
After updating, you may have to wait a few seconds and refresh the page to see
the updated reference.
</Text>
<Group>
<Button
onClick={() => {
onUpdateReference(record);
setWasUpdated(true);
close();
}}
color="red"
>
Yes, update reference
</Button>
<Button onClick={close} variant="default" autoFocus>
Cancel
</Button>
</Group>
</Stack>
</Modal>

<Box p={'md'} bg={'dark.8'}>
<Table
horizontalSpacing={'xs'}
verticalSpacing={4}
withTableBorder
withColumnBorders
>
<Table.Thead>
<Table.Tr>
<Table.Th>Timestamp</Table.Th>
<Table.Th>Error</Table.Th>
<Table.Th>Commit</Table.Th>
<Table.Th>Timing</Table.Th>
<Table.Th>Log</Table.Th>
<Table.Th>Candidate</Table.Th>
<Table.Th>Reference</Table.Th>
<Table.Th>Difference</Table.Th>
<Table.Th />
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Box>
</Table.Thead>
<Table.Tbody>
{testData.map((d: TestData, i: number) => (
<Table.Tr key={d.timeStamp}>
<Table.Td>
<Text size={'sm'} c={'dimmed'}>
{new Date(d.timeStamp).toISOString().split('T')[0]}
<br />
{new Date(d.timeStamp).toISOString().split('T')[1]?.replace('Z', '')}
</Text>
</Table.Td>
<Table.Td>
<PixelDiffNumber value={d.pixelError} />
</Table.Td>
<Table.Td>
<Anchor
href={`https://github.com/OpenSpace/OpenSpace/commit/${d.commitHash}`}
target={'_blank'}
>
{d.commitHash.substring(0, 8)}
</Anchor>
</Table.Td>
<Table.Td>
<Text>{timingDisplay(d.timing)}</Text>
</Table.Td>
<Table.Td>
<Anchor
href={`/api/result/log/${record.group}/${record.name}/${record.hardware}/${d.timeStamp}`}
target={'_blank'}
>
Log ({d.nErrors} errors)
</Anchor>
</Table.Td>
<Table.Td>
<ImageThumbnail
type={'candidate'}
group={record.group}
name={record.name}
hardware={record.hardware}
timestamp={d.timeStamp}
width={ImageWidth}
/>
</Table.Td>
<Table.Td>
<ImageThumbnail
type={'reference'}
group={record.group}
name={record.name}
hardware={record.hardware}
timestamp={d.timeStamp}
width={ImageWidth}
/>
</Table.Td>
<Table.Td>
<ImageThumbnail
type={'difference'}
group={record.group}
name={record.name}
hardware={record.hardware}
timestamp={d.timeStamp}
width={ImageWidth}
/>
</Table.Td>
<Table.Td>
{i === 0 && (
<>
<Button variant={'default'} onClick={open} disabled={wasUpdated}>
Upgrade Candidate to Reference
</Button>
</>
)}
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Box>
</>
);
}
Loading