Skip to content

Commit

Permalink
feat: mapping update
Browse files Browse the repository at this point in the history
  • Loading branch information
mjaquiery committed Apr 24, 2024
1 parent 8f6d969 commit d3e749a
Show file tree
Hide file tree
Showing 8 changed files with 607 additions and 277 deletions.
17 changes: 10 additions & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import {useState} from "react";
import FetchResourceContextProvider from "./Components/FetchResourceContext";
import AttachmentUploadContextProvider from "./Components/AttachmentUploadContext";
import Mapping from "./Components/Mapping";
import {Theme} from "@mui/material/styles";
import Paper from "@mui/material/Paper";

export const pathMatches = (path: string, pathname: string) => matchPath({path: path, end: true}, pathname) !== null

Expand Down Expand Up @@ -86,28 +88,28 @@ export function Core() {
</ListItemIcon>
<ListItemText primary="Dashboard" />
</ListItemButton>
<Divider component="li" key="div0" />
<Divider component="li" key="div0">{open && "Outputs"}</Divider>
{[LOOKUP_KEYS.EXPERIMENT, LOOKUP_KEYS.CYCLER_TEST].map(lookupKey => <LI key={lookupKey} lookupKey={lookupKey} />)}
<Divider component="li" key="div1" />
<Divider component="li" key="div1">{open && "Data"}</Divider>
{[
LOOKUP_KEYS.FILE,
LOOKUP_KEYS.COLUMN_FAMILY,
LOOKUP_KEYS.UNIT
].map(lookupKey => <LI key={lookupKey} lookupKey={lookupKey} />)}
<Divider component="li" key="div2" />
<Divider component="li" key="div2">{open && "Resources"}</Divider>
{[
LOOKUP_KEYS.CELL,
LOOKUP_KEYS.EQUIPMENT,
LOOKUP_KEYS.SCHEDULE,
LOOKUP_KEYS.ARBITRARY_FILE
].map(lookupKey => <LI key={lookupKey} lookupKey={lookupKey} />)}
<Divider component="li" key="div3" />
<Divider component="li" key="div3">{open && "Inputs"}</Divider>
{[
LOOKUP_KEYS.HARVESTER,
LOOKUP_KEYS.PATH,
LOOKUP_KEYS.VALIDATION_SCHEMA,
].map(lookupKey => <LI key={lookupKey} lookupKey={lookupKey} />)}
<Divider component="li" key="div4" />
<Divider component="li" key="div4">{open && "Groups"}</Divider>
{[
LOOKUP_KEYS.LAB,
LOOKUP_KEYS.TEAM,
Expand Down Expand Up @@ -161,7 +163,9 @@ export function Core() {
<FilterContextProvider>
<FilterBar key="filter_bar" />
<SelectedResourcesPane />
<Outlet key="main_content" />
<Paper className={clsx(classes.mainPaper)} elevation={0}>
<Outlet key="main_content" />
</Paper>
</FilterContextProvider>
</main>
<SnackbarMessenger autoHideDuration={6000} />
Expand Down Expand Up @@ -199,7 +203,6 @@ export function Core() {
lookup_key={lookup_key ?? "CYCLER_TEST"}
expanded={true}
editing={searchParams.get('editing') === 'true'}
sx={{margin: (t) => t.spacing(1)}}
/>
}

Expand Down
607 changes: 362 additions & 245 deletions src/Components/Mapping.tsx

Large diffs are not rendered by default.

87 changes: 71 additions & 16 deletions src/Components/ResourceStatuses.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {Link} from "react-router-dom";
import List from "@mui/material/List";
import useStyles from "../styles/UseStyles";
import CardActions from "@mui/material/CardActions";
import {ObservedFile} from "@battery-intelligence-lab/galv";
import clsx from "clsx";

export function StatusAlert(
{message, fix_button, children, ...alertProps}:
Expand All @@ -31,29 +33,82 @@ export function StatusAlert(
</Alert>
}

const fileStatuses = (file: ObservedFile) => {
const statuses: ReactNode[] = []
const mappings = file.applicable_mappings as {name: string, url: string, is_valid: boolean}[]
const map = mappings.find(m => m.url === file.mapping)
if (mappings.length === 0) {
statuses.push(<StatusAlert
message="There are no mappings that can be applied to this file"
fix_button={
<Button component={Link} to={`${PATHS.MAPPING}/${file.id ?? file.id}`} size="small">
Create mapping
</Button>
}
severity="error"
>
Mappings are used to map the columns in a file to the columns in the database.
When a suite of files use the same column names to represent the same kind of data,
analyses can be performed across all the files.
Galv requires that certain key columns are present in every file: 'ElapsedTime_s', 'Voltage_V', and 'Current_A'.
</StatusAlert>)
} else if (!map) {
statuses.push(<StatusAlert
message="There are mappings that can be applied to this file, but none have been selected"
fix_button={
<Button component={Link} to={`${PATHS.MAPPING}/${file.id ?? file.id}`} size="small">
Choose mapping
</Button>
}
severity="warning"
>

Mappings are used to map the columns in a file to the columns in the database.
When a suite of files use the same column names to represent the same kind of data,
analyses can be performed across all the files.
Galv requires that certain key columns are present in every file: 'ElapsedTime_s', 'Voltage_V', and 'Current_A'.
</StatusAlert>)
} else if (!map.is_valid) {
statuses.push(<StatusAlert
message={`An invalid mapping '${map.name}' is applied to this file`}
fix_button={
<Button component={Link} to={`${PATHS.MAPPING}/${file.id ?? file.id}`} size="small">
Edit mapping
</Button>
}
severity="warning"
>
All mappings must recognise 'ElapsedTime_s', 'Voltage_V', and 'Current_A' columns to be counted as valid.
Data that do not have these columns cannot be previewed, and may not be suitable for analysis.
</StatusAlert>)
} else {
statuses.push(<StatusAlert
message={`File mapped using valid mapping '${map.name}'`}
fix_button={
<Button component={Link} to={`${PATHS.MAPPING}/${file.id ?? file.id}`} size="small">
Edit mapping
</Button>
}
severity="success"
>
Data has the required columns. Preview and analysis tools are available.
</StatusAlert>)
}
return statuses
}

export default function ResourceStatuses({lookup_key}: {lookup_key: LookupKey}) {
const {classes} = useStyles()
const {apiResource} = useApiResource()
if (!apiResource) return null
const statuses: ReactNode[] = []
const get_prop = (prop: string) => apiResource[prop] ?? undefined
switch(lookup_key) {
case LOOKUP_KEYS.FILE:
if (!get_prop("has_required_columns")) {
statuses.push(<StatusAlert
message="This file does not have the required columns."
fix_button={
<Button component={Link} to={`${PATHS.MAPPING}/${apiResource.id ?? apiResource.id}`}>
Map columns
</Button>
}
severity="warning"
>
All files are required to have 'ElapsedTime_s', 'Voltage_V', and 'Current_A' columns to be counted as valid.
Files that do not have these columns cannot be previewed, and may not be suitable for analysis.
</StatusAlert>)
}
statuses.push(...fileStatuses(apiResource as unknown as ObservedFile))
break;
}
return statuses.length > 0?
<CardActions><List sx={{width: "100%"}}>{...statuses}</List></CardActions> : null
<CardActions className={clsx(classes.statusActions)}>
<List>{...statuses}</List>
</CardActions> : null
}
2 changes: 1 addition & 1 deletion src/Components/misc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@ export function get_url_components(url: string):

export function deep_copy<T extends Serializable>(obj: T): T {
return JSON.parse(JSON.stringify(obj))
}
}
3 changes: 2 additions & 1 deletion src/Components/prettify/TypeChanger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,10 @@ function TypeChangePopover({value, onTypeChange, ...props}: TypeChangerPopoverPr
(node: HTMLElement|null) => setResourcePopoverAnchorEl(node),
[]
)
const is_resource_type = (v: typeof value) => Object.keys(type_map).reduce((acc, k) => acc || k === v, false)
// Reopen child popover if value is a resource type
useEffect(() => {
if (props.open && value && Object.keys(API_HANDLERS).map(key_to_type).includes(value)) {
if (props.open && value && is_resource_type(value)) {
setResourcePopoverOpen(true)
}
}, [props.open, value]);
Expand Down
12 changes: 10 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,8 +514,9 @@ export const FIELDS = {
columns: {readonly: true, type: key_to_type(LOOKUP_KEYS.COLUMN), many: true, fetch_in_download: true},
upload_info: {readonly: true, type: "string"},
parquet_partitions: {readonly: true, type: key_to_type(LOOKUP_KEYS.PARQUET_PARTITION), many: true},
applicable_mappings: {readonly: true, type: key_to_type(LOOKUP_KEYS.MAPPING), many: true},
mapping: {readonly: true, type: key_to_type(LOOKUP_KEYS.MAPPING)},
applicable_mappings: {readonly: true, type: 'object', many: true, priority: PRIORITY_LEVELS.HIDDEN},
mapping: {readonly: true, type: key_to_type(LOOKUP_KEYS.MAPPING), priority: PRIORITY_LEVELS.SUMMARY},
summary: {readonly: true, type: "object", priority: PRIORITY_LEVELS.HIDDEN},
},
[LOOKUP_KEYS.MAPPING]: {
...generic_fields,
Expand Down Expand Up @@ -964,4 +965,11 @@ alongside an indication of their validation status.
If you see problems on your dashboard, you should check the relevant resource for more information.
`,
MAPPING: `
Mappings are used to map the columns in a [file](${PATHS[LOOKUP_KEYS.FILE]}) to the columns in the database.
This allows Galv to understand the data in the file, and to store it in the database.
When a suite of files use the same column names to represent the same kind of data,
analyses can be performed across all the files.
`,
} as const
67 changes: 62 additions & 5 deletions src/styles/UseStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// of Oxford, and the 'Galv' Developers. All rights reserved.

import { makeStyles } from 'tss-react/mui';
import {Theme} from "@mui/material/styles";
import {alpha, Theme} from "@mui/material/styles";

const item = (theme: Theme) => ({
" .MuiCardHeader-root": {
Expand Down Expand Up @@ -42,9 +42,12 @@ export default makeStyles()((theme) => {
loginPaper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
flexDirection: "column",
alignItems: 'center',
},
mainPaper: {
padding: theme.spacing(1),
},
icon: {
margin: theme.spacing(1),
width: theme.spacing(7),
Expand Down Expand Up @@ -164,7 +167,6 @@ export default makeStyles()((theme) => {
},
filterBar: {
justifyContent: "space-between",
margin: theme.spacing(0.5),
maxHeight: theme.spacing(30),
overflowY: "auto",
"& .horizontal": {
Expand Down Expand Up @@ -270,8 +272,39 @@ export default makeStyles()((theme) => {
},
typeChangerResourcePopover: {},
mappingTable: {
paddingTop: theme.spacing(2),
'& td > svg': {verticalAlign: "middle"},
},
mappingWarnings: {},
mappingSectionHeader: {
fontWeight: "bold",
fontSize: "large",
'& .MuiTypography-root': {
display: "inline-block",
}
},
mappingSectionHeaderClickable: {
cursor: "pointer",
},
mappingHeaderComment: {
marginLeft: theme.spacing(2),
fontSize: "small",
fontWeight: "normal",
display: "inline-flex",
justifyContent: "center",
alignItems: "center",
},
mappingRowCountSelector: {
'& .MuiButtonBase-root': {
padding: 0,
marginLeft: theme.spacing(0.5),
marginRight: theme.spacing(0.5),
},
},
mappingHeaderSlider: {
marginLeft: theme.spacing(2),
maxWidth: "10em",
},
mappingTableHeadRow: {
'& th, & td': {
fontWeight: "bold",
Expand All @@ -286,10 +319,34 @@ export default makeStyles()((theme) => {
mappingDefaultColumn: {
'& span': {fontWeight: "bold"}
},
mappingRebase: {minWidth: "18em"},
mappingRebase: {
minWidth: "9em",
fontFamily: "math",
'& input, & div': {fontFamily: "math"}
},
mappingRebaseEdit: {
minWidth: "18em",
},
mappingNumberInput: {
'& input': {padding: 0, paddingTop: 0, paddingBottom: 0, paddingLeft: 0, paddingRight: 0},
'& input, & div': {fontFamily: "math"}
},
mappingInitial: {
backgroundColor: alpha(theme.palette.info.main, 0.1),
},
mappingProcess: {
backgroundColor: alpha(theme.palette.warning.main, 0.1),
},
mappingResult: {
backgroundColor: alpha(theme.palette.success.main, 0.1),
},
statusActions: {
'& .MuiList-root': {
width: "100%",
},
'& .MuiButton-root': {
paddingTop: 0,
paddingBottom: 0,
}
},
}
});
Loading

0 comments on commit d3e749a

Please sign in to comment.