Merge branch 'development' into main
This commit is contained in:
@@ -5,6 +5,7 @@ jest.mock("../Contexts/UserContext");
|
|||||||
jest.mock("../Contexts/ManifestsContext");
|
jest.mock("../Contexts/ManifestsContext");
|
||||||
jest.mock("../Contexts/CarUpdatesContext");
|
jest.mock("../Contexts/CarUpdatesContext");
|
||||||
jest.mock("../../services/monitoring");
|
jest.mock("../../services/monitoring");
|
||||||
|
jest.mock("../../services/grafana");
|
||||||
|
|
||||||
import { render, screen, cleanup, waitFor, waitForElementToBeRemoved } from "@testing-library/react";
|
import { render, screen, cleanup, waitFor, waitForElementToBeRemoved } from "@testing-library/react";
|
||||||
import { setToken } from "../Contexts/UserContext";
|
import { setToken } from "../Contexts/UserContext";
|
||||||
@@ -57,11 +58,11 @@ describe("App", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Route / unauthenticated", async () => {
|
it("Route / unauthenticated", async () => {
|
||||||
await check("/", "span.MuiButton-label", "Sign In");
|
await sleepAndCheck("/", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /home unauthenticated", async () => {
|
it("Route /home unauthenticated", async () => {
|
||||||
await check("/home", "span.MuiButton-label", "Sign In");
|
await sleepAndCheck("/home", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /vehicle-add unauthenticated", async () => {
|
it("Route /vehicle-add unauthenticated", async () => {
|
||||||
@@ -85,7 +86,7 @@ describe("App", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Route /datascope unauthenticated", async () => {
|
it("Route /datascope unauthenticated", async () => {
|
||||||
await check("/datascope", "span.MuiButton-label", "Sign In");
|
await sleepAndCheck("/datascope", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /datascope/battery unauthenticated", async () => {
|
it("Route /datascope/battery unauthenticated", async () => {
|
||||||
@@ -110,12 +111,12 @@ describe("App", () => {
|
|||||||
|
|
||||||
it("Route / authenticated", async () => {
|
it("Route / authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await sleepAndCheck("/", "h1", "Welcome John!");
|
await sleepAndCheck("/", "h6", "Home");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /home authenticated", async () => {
|
it("Route /home authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await sleepAndCheck("/home", "h1", "Welcome John!");
|
await sleepAndCheck("/home", "h6", "Home");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /vehicle-add authenticated", async () => {
|
it("Route /vehicle-add authenticated", async () => {
|
||||||
@@ -154,7 +155,7 @@ describe("App", () => {
|
|||||||
|
|
||||||
it("Route /datascope authenticated", async () => {
|
it("Route /datascope authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/datascope", "h6", "Datascope");
|
await sleepAndCheck("/datascope", "h6", "Datascope");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /datascope/battery authenticated", async () => {
|
it("Route /datascope/battery authenticated", async () => {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -105,7 +105,7 @@ const MainForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
<div className={`${classes.paper} ${classes.tableSize}`}>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@@ -125,7 +125,7 @@ const MainForm = () => {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
{row.status}
|
{row.status}
|
||||||
{row.progress > 0 && (
|
{row.progress > -1 && (
|
||||||
<LinearProgress variant="determinate" value={row.progress} />
|
<LinearProgress variant="determinate" value={row.progress} />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -1,158 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import { FormControl, InputLabel, Select } from "@material-ui/core";
|
|
||||||
|
|
||||||
import {
|
|
||||||
useVehicleContext,
|
|
||||||
VehicleProvider,
|
|
||||||
} from "../../Contexts/VehicleContext";
|
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
|
||||||
import useStyles from "../../useStyles";
|
|
||||||
|
|
||||||
const Control = (props) => {
|
|
||||||
const classes = useStyles();
|
|
||||||
const { models, years, vehicles, getModels, getYears, getVehicles } =
|
|
||||||
useVehicleContext();
|
|
||||||
const {
|
|
||||||
token: {
|
|
||||||
idToken: { jwtToken: token },
|
|
||||||
},
|
|
||||||
} = useUserContext();
|
|
||||||
const [model, setModel] = useState("");
|
|
||||||
const [year, setYear] = useState(-1);
|
|
||||||
const [trim, setTrim] = useState("");
|
|
||||||
|
|
||||||
const handleChangeModel = (event) => {
|
|
||||||
setModel(event.target.value);
|
|
||||||
};
|
|
||||||
const handleChangeYear = (event) => {
|
|
||||||
setYear(event.target.value);
|
|
||||||
};
|
|
||||||
const handleChangeTrim = (event) => {
|
|
||||||
setTrim(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!token) return;
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
await getModels(token);
|
|
||||||
await getYears(token);
|
|
||||||
} catch (e) {}
|
|
||||||
})();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [token]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!models || models.length === 0) return;
|
|
||||||
setModel(models[0]);
|
|
||||||
}, [models]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!years || years.length === 0) return;
|
|
||||||
setYear(years[0]);
|
|
||||||
}, [years]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (model === null || year === -1) return;
|
|
||||||
getVehicles({ model, year, trim }, token);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [model, year, trim]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!props.onSelection) return;
|
|
||||||
const vins = vehicles.map((item) => item.vin);
|
|
||||||
props.onSelection(vins);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [vehicles]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.paper}>
|
|
||||||
<div className={classes.form}>
|
|
||||||
<FormControl className={classes.formControlInline} variant="outlined">
|
|
||||||
<InputLabel htmlFor="car-model" style={{ backgroundColor: "White" }}>
|
|
||||||
Model
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
native
|
|
||||||
value={model}
|
|
||||||
onChange={handleChangeModel}
|
|
||||||
variant="outlined"
|
|
||||||
inputProps={{
|
|
||||||
name: "car-model",
|
|
||||||
id: "car-model",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{models.map((item) => (
|
|
||||||
<option key={item} value={item}>
|
|
||||||
{item}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormControl className={classes.formControlInline} variant="outlined">
|
|
||||||
<InputLabel htmlFor="car-year" style={{ backgroundColor: "White" }}>
|
|
||||||
Year
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
native
|
|
||||||
value={year}
|
|
||||||
onChange={handleChangeYear}
|
|
||||||
variant="outlined"
|
|
||||||
inputProps={{
|
|
||||||
name: "car-year",
|
|
||||||
id: "car-year",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{years.map((item) => (
|
|
||||||
<option key={item} value={item}>
|
|
||||||
{item}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormControl className={classes.formControlInline} variant="outlined">
|
|
||||||
<InputLabel htmlFor="car-trim" style={{ backgroundColor: "White" }}>
|
|
||||||
Trim
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
native
|
|
||||||
value={year}
|
|
||||||
onChange={handleChangeTrim}
|
|
||||||
variant="outlined"
|
|
||||||
inputProps={{
|
|
||||||
name: "car-trim",
|
|
||||||
id: "car-trim",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{years.map((item) => (
|
|
||||||
<option key={item} value={item}>
|
|
||||||
{item}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<div className={classes.labelInline}>
|
|
||||||
{vehicles.length === 0
|
|
||||||
? "No Cars Selected"
|
|
||||||
: vehicles.length === 1
|
|
||||||
? "1 Car Selected"
|
|
||||||
: `${vehicles.length} Cars Selected`}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={classes.form}></div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CarSelection = (props) => (
|
|
||||||
<VehicleProvider>
|
|
||||||
<Control {...props} />
|
|
||||||
</VehicleProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
CarSelection.propTypes = {
|
|
||||||
onSelection: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CarSelection;
|
|
||||||
@@ -114,7 +114,7 @@ const CarSelectionTable = (props) => {
|
|||||||
}, [pageIndex, pageSize, orderBy, order, search, token]);
|
}, [pageIndex, pageSize, orderBy, order, search, token]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
<div className={`${classes.paper} ${classes.tableSize}`}>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeaderSortable
|
<TableHeaderSortable
|
||||||
classes={classes}
|
classes={classes}
|
||||||
|
|||||||
@@ -75,9 +75,13 @@ const SendCommand = ({ vins }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.form}>
|
<div className={`${classes.form}`} style={{ marginTop: 20 }}>
|
||||||
<FormControl className={classes.formControlInline} variant="outlined">
|
<FormControl
|
||||||
<InputLabel htmlFor="send-command" style={{ backgroundColor: "White" }}>
|
className={classes.formControlInline}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<InputLabel htmlFor="send-command" className={classes.whiteBackground}>
|
||||||
Command
|
Command
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@@ -97,10 +101,14 @@ const SendCommand = ({ vins }) => {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl className={classes.formControlInline} variant="outlined">
|
<FormControl
|
||||||
|
className={classes.formControlInline}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
<InputLabel
|
<InputLabel
|
||||||
htmlFor="send-parameter"
|
htmlFor="send-parameter"
|
||||||
style={{ backgroundColor: "White" }}
|
className={classes.whiteBackground}
|
||||||
>
|
>
|
||||||
Parameter
|
Parameter
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
@@ -127,6 +135,7 @@ const SendCommand = ({ vins }) => {
|
|||||||
aria-label="send command"
|
aria-label="send command"
|
||||||
component="span"
|
component="span"
|
||||||
onClick={clickHandler}
|
onClick={clickHandler}
|
||||||
|
size="small"
|
||||||
disabled={busy || vins.length === 0}
|
disabled={busy || vins.length === 0}
|
||||||
>
|
>
|
||||||
<SendIcon fontSize="large" />
|
<SendIcon fontSize="large" />
|
||||||
|
|||||||
@@ -54,19 +54,21 @@ const MainForm = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
<div className={`${classes.paper} ${classes.tableSize}`}>
|
||||||
<Grid container className={classes.root} spacing={2}>
|
<Grid container className={classes.root} spacing={2}>
|
||||||
<Grid item md={6}>
|
<Grid item md={4} className={classes.textJustifyAlign}>
|
||||||
<Link to="/vehicle-add" className={classes.labelInline}>
|
<Link to="/vehicle-add">
|
||||||
<AddCircleIcon fontSize="large" />
|
<AddCircleIcon fontSize="large" />
|
||||||
</Link>
|
</Link>
|
||||||
<SearchField classes={classes} onSearch={handleSearch} />
|
|
||||||
<div
|
<div
|
||||||
className={classes.labelInline}
|
className={classes.labelInline}
|
||||||
>{`${selected.length} Selected`}</div>
|
>{`${selected.length} Selected`}</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={6} style={{ textAlign: "right" }}>
|
<Grid item md={4} className={classes.textCenterAlign}>
|
||||||
<SendCommand vins={selected} style={{ display: "flex" }} />
|
<SearchField classes={classes} onSearch={handleSearch} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={4} className={classes.textRightAlign}>
|
||||||
|
<SendCommand vins={selected} />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<CarSelectionTable
|
<CarSelectionTable
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useState } from "react";
|
||||||
|
|
||||||
import api from "../../services/updates";
|
import api from "../../services/updates";
|
||||||
|
import { validateStatusMessage } from "../../utils/statusMessage";
|
||||||
|
|
||||||
|
const FINAL_UPDATE_STATES = ["package_install_complete"];
|
||||||
const CarUpdatesContext = React.createContext();
|
const CarUpdatesContext = React.createContext();
|
||||||
|
|
||||||
const validateDeployCarUpdates = (data) => {
|
const validateDeployCarUpdates = (data) => {
|
||||||
@@ -49,6 +51,8 @@ export const CarUpdatesProvider = ({ children }) => {
|
|||||||
result = await api.getCarUpdates(search, token);
|
result = await api.getCarUpdates(search, token);
|
||||||
if (result.error)
|
if (result.error)
|
||||||
throw new Error(`Get car updates error. ${result.message}`);
|
throw new Error(`Get car updates error. ${result.message}`);
|
||||||
|
result.data.forEach((item) => (item.progress = 0));
|
||||||
|
console.log(result.data);
|
||||||
setCarUpdates(result.data);
|
setCarUpdates(result.data);
|
||||||
if (search && search.offset === 0 && result.total) {
|
if (search && search.offset === 0 && result.total) {
|
||||||
setTotalCarUpdates(result.total);
|
setTotalCarUpdates(result.total);
|
||||||
@@ -75,30 +79,48 @@ export const CarUpdatesProvider = ({ children }) => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDownloadProgress = (status) => {
|
||||||
|
if (status.package_total > 0)
|
||||||
|
return Math.floor((100 * status.package_current) / status.package_total);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInstallProgress = (status) => {
|
||||||
|
if (status.total_files > 0)
|
||||||
|
return Math.floor((100 * status.installed) / status.total_files);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
const applyProgressStatus = (item, status) => {
|
const applyProgressStatus = (item, status) => {
|
||||||
if (status.msg === "package_download_complete") {
|
if (validateStatusMessage(status)) {
|
||||||
delete item.progress;
|
if (status.msg === "downloading") {
|
||||||
item.status = "downloaded";
|
item.progress = getDownloadProgress(status);
|
||||||
} else if (status.msg === "downloading" && status.package_total > 0) {
|
item.status = `downloading ${item.progress}%`;
|
||||||
let progress = Math.floor(
|
return;
|
||||||
(100 * status.package_current) / status.package_total
|
} else if (status.msg === "package_download_complete") {
|
||||||
);
|
item.progress = 100;
|
||||||
if (progress > 99) progress = 0;
|
item.status = "downloaded";
|
||||||
item.progress = progress;
|
return;
|
||||||
item.status = `downloading ${progress}%`;
|
} else if (status.msg === "installing") {
|
||||||
} else if (status.error > 0 || status.msg === "download_error") {
|
item.progress = getInstallProgress(status);
|
||||||
item.status = "download error";
|
item.status = `installing ${item.progress}%`;
|
||||||
} else {
|
return;
|
||||||
item.status = "downloading";
|
} else if (status.msg === "package_install_complete") {
|
||||||
|
item.progress = 100;
|
||||||
|
item.status = "installed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
delete item.progress;
|
||||||
|
item.status = status.msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyProgressStatuses = (statuses) => {
|
const applyProgressStatuses = (statuses) => {
|
||||||
let items = JSON.parse(JSON.stringify(carUpdates));
|
let items = JSON.parse(JSON.stringify(carUpdates));
|
||||||
|
|
||||||
statuses.forEach((status) => {
|
statuses.forEach((status) => {
|
||||||
let item = items.find((item) => status.id === item.id);
|
let item = items.find((item) => status.car_update_id === item.id);
|
||||||
if (!item || status.id === 0) return;
|
if (!item || status.car_update_id === 0) return;
|
||||||
applyProgressStatus(item, status);
|
applyProgressStatus(item, status);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -113,7 +135,7 @@ export const CarUpdatesProvider = ({ children }) => {
|
|||||||
try {
|
try {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
const carupdateids = carUpdates.reduce((accum, update) => {
|
const carupdateids = carUpdates.reduce((accum, update) => {
|
||||||
if (update.status !== "downloaded") accum.push(update.id);
|
if (update.status !== "installed") accum.push(update.id);
|
||||||
return accum;
|
return accum;
|
||||||
}, []);
|
}, []);
|
||||||
if (carupdateids.length === 0) return;
|
if (carupdateids.length === 0) return;
|
||||||
@@ -138,7 +160,7 @@ export const CarUpdatesProvider = ({ children }) => {
|
|||||||
return 1000;
|
return 1000;
|
||||||
}
|
}
|
||||||
for (let i = 0, len = carUpdates.length; i < len; i++) {
|
for (let i = 0, len = carUpdates.length; i < len; i++) {
|
||||||
if (carUpdates[i].status.indexOf("downloading") > -1) return 1000;
|
if (FINAL_UPDATE_STATES.indexOf(carUpdates[i].status) === -1) return 1000;
|
||||||
}
|
}
|
||||||
return 10000;
|
return 10000;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Typography } from "@material-ui/core";
|
|
||||||
import useStyles from "../useStyles";
|
import useStyles from "../useStyles";
|
||||||
|
|
||||||
import { useUserContext } from "../Contexts/UserContext";
|
|
||||||
import { useStatusContext } from "../Contexts/StatusContext";
|
import { useStatusContext } from "../Contexts/StatusContext";
|
||||||
import VehicleMap from "../VehicleMap";
|
import VehicleMap from "../VehicleMap";
|
||||||
import { getName } from "../../utils/jwt";
|
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { token } = useUserContext();
|
|
||||||
const { setTitle, setSitePath } = useStatusContext();
|
const { setTitle, setSitePath } = useStatusContext();
|
||||||
|
|
||||||
|
|
||||||
@@ -21,9 +17,6 @@ const Home = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper}>
|
<div className={classes.paper}>
|
||||||
<Typography className={classes.homePageTitle} component="h1" variant="h5">
|
|
||||||
Welcome {getName(token)}!
|
|
||||||
</Typography>
|
|
||||||
<VehicleMap />
|
<VehicleMap />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ export default function MenuDrawer({ children }) {
|
|||||||
paper: classes.drawerPaper,
|
paper: classes.drawerPaper,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={classes.drawerHeader}>
|
<div
|
||||||
|
className={`${classes.drawerHeader} ${classes.drawerHeaderLogo}`}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={logo}
|
src={logo}
|
||||||
alt="Fisker Admin Portal"
|
alt="Fisker Admin Portal"
|
||||||
@@ -69,9 +71,7 @@ export default function MenuDrawer({ children }) {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className={classes.drawerHeader} />
|
<div className={classes.drawerHeader} />
|
||||||
<Container component="main" maxWidth="lg">
|
<Container component="main">{children}</Container>
|
||||||
{children}
|
|
||||||
</Container>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -182,11 +182,10 @@ exports[`SideMenu Authenticated 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
|
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root makeStyles-menuExternalLink-53 MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
|
||||||
href="https://grafana.fiskerdps.com"
|
href="https://grafana.fiskerdps.com"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
role="button"
|
role="button"
|
||||||
style="text-decoration: inherit;"
|
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,31 +5,20 @@ import ListItemIcon from "@material-ui/core/ListItemIcon";
|
|||||||
import ListItemText from "@material-ui/core/ListItemText";
|
import ListItemText from "@material-ui/core/ListItemText";
|
||||||
import { Link } from "@material-ui/core";
|
import { Link } from "@material-ui/core";
|
||||||
|
|
||||||
|
import useStyles from "./useStyles";
|
||||||
|
|
||||||
function ListItemExternalLink(props) {
|
function ListItemExternalLink(props) {
|
||||||
const { icon, primary, url } = props;
|
const { icon, primary, url } = props;
|
||||||
const style = {
|
const classes = useStyles();
|
||||||
textDecoration: "inherit",
|
|
||||||
color: "inherit",
|
|
||||||
"&:link": {
|
|
||||||
textDecoration: "inherit",
|
|
||||||
color: "inherit",
|
|
||||||
cursor: "auto",
|
|
||||||
},
|
|
||||||
"&:visited": {
|
|
||||||
textDecoration: "inherit",
|
|
||||||
color: "inherit",
|
|
||||||
cursor: "auto",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
button
|
button
|
||||||
component={Link}
|
component={Link}
|
||||||
style={style}
|
|
||||||
href={url}
|
href={url}
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
className={classes.menuExternalLink}
|
||||||
>
|
>
|
||||||
{icon ? <ListItemIcon>{icon}</ListItemIcon> : null}
|
{icon ? <ListItemIcon>{icon}</ListItemIcon> : null}
|
||||||
<ListItemText primary={primary} />
|
<ListItemText primary={primary} />
|
||||||
|
|||||||
@@ -126,17 +126,18 @@ const MainForm = () => {
|
|||||||
<form className={classes.form} noValidate action="{onSubmit}">
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
<Typography variant="body2">Created {createDate}.</Typography>
|
<Typography variant="body2">Created {createDate}.</Typography>
|
||||||
<Grid container className={classes.root} spacing={2}>
|
<Grid container className={classes.root} spacing={2}>
|
||||||
<Grid item md={10}>
|
<Grid item md={2}>
|
||||||
<SearchField classes={classes} onSearch={handleSearch} />
|
|
||||||
<div
|
<div
|
||||||
className={classes.labelInline}
|
className={classes.labelInline}
|
||||||
>{`${selected.length} Selected`}</div>
|
>{`${selected.length} Selected`}</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={2} style={{ textAlign: "right" }}>
|
<Grid item md={8} className={classes.textCenterAlign}>
|
||||||
|
<SearchField classes={classes} onSearch={handleSearch} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={2} className={classes.textRightAlign}>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={busy || selected.length === 0}
|
disabled={busy || selected.length === 0}
|
||||||
fullWidth
|
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
className={classes.formControl}
|
className={classes.formControl}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
|
Grid,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableFooter,
|
TableFooter,
|
||||||
TablePagination,
|
TablePagination,
|
||||||
TableRow,
|
TableRow,
|
||||||
Toolbar,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import AddCircleIcon from "@material-ui/icons/AddCircle";
|
import AddCircleIcon from "@material-ui/icons/AddCircle";
|
||||||
@@ -184,13 +184,18 @@ const MainForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
<div className={`${classes.paper} ${classes.tableSize}`}>
|
||||||
<Toolbar className={classes.tableToolbar}>
|
<Grid container className={classes.root} spacing={2}>
|
||||||
<Link to="/package-create" className={classes.labelInline}>
|
<Grid item md={4} className={classes.textJustifyAlign}>
|
||||||
<AddCircleIcon fontSize="large" />
|
<Link to="/package-create" className={classes.labelInline}>
|
||||||
</Link>
|
<AddCircleIcon fontSize="large" />
|
||||||
<SearchField classes={classes} onSearch={handleSearch} />
|
</Link>
|
||||||
</Toolbar>
|
</Grid>
|
||||||
|
<Grid item md={4} className={classes.textCenterAlign}>
|
||||||
|
<SearchField classes={classes} onSearch={handleSearch} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={4} className={classes.textRightAlign}></Grid>
|
||||||
|
</Grid>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeaderSortable
|
<TableHeaderSortable
|
||||||
classes={classes}
|
classes={classes}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ const MainForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
<div className={`${classes.paper} ${classes.tableSize}`}>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@@ -139,7 +139,7 @@ const MainForm = () => {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
{row.status}
|
{row.status}
|
||||||
{row.progress > 0 && (
|
{row.progress > -1 && (
|
||||||
<LinearProgress variant="determinate" value={row.progress} />
|
<LinearProgress variant="determinate" value={row.progress} />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
exports[`Sign In Form Should render 1`] = `
|
exports[`Sign In Form Should render 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="makeStyles-paper-3"
|
class="makeStyles-paper-3 makeStyles-textJustifyAlign-48"
|
||||||
style="justify-content: center;"
|
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default function SignInForm() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper} style={{ justifyContent: "center" }}>
|
<div className={`${classes.paper} ${classes.textJustifyAlign}`}>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
labelInline: {
|
labelInline: {
|
||||||
fontSize: "1.25em",
|
fontSize: "1.25em",
|
||||||
margin: theme.spacing(2, 1, 1),
|
margin: theme.spacing(4, 1, 1),
|
||||||
display: "inline-flex",
|
display: "inline-flex",
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
verticalAlign: "bottom",
|
verticalAlign: "bottom",
|
||||||
@@ -77,6 +77,8 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
easing: theme.transitions.easing.sharp,
|
easing: theme.transitions.easing.sharp,
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
}),
|
}),
|
||||||
|
color: "black",
|
||||||
|
backgroundColor: "white",
|
||||||
},
|
},
|
||||||
appBarShift: {
|
appBarShift: {
|
||||||
width: `calc(100% - ${DRAWER_WIDTH}px)`,
|
width: `calc(100% - ${DRAWER_WIDTH}px)`,
|
||||||
@@ -107,6 +109,9 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
...theme.mixins.toolbar,
|
...theme.mixins.toolbar,
|
||||||
justifyContent: "flex-start",
|
justifyContent: "flex-start",
|
||||||
},
|
},
|
||||||
|
drawerHeaderLogo: {
|
||||||
|
backgroundColor: "#3f51b5",
|
||||||
|
},
|
||||||
content: {
|
content: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
padding: theme.spacing(3),
|
padding: theme.spacing(3),
|
||||||
@@ -231,6 +236,32 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
datascopeContainerValue: {
|
datascopeContainerValue: {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
},
|
},
|
||||||
|
textJustifyAlign: { textAlign: "justifyContent" },
|
||||||
|
textCenterAlign: { textAlign: "center" },
|
||||||
|
textRightAlign: { textAlign: "right" },
|
||||||
|
fullWidth: { width: "100%" },
|
||||||
|
pageCenter: {
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
},
|
||||||
|
menuExternalLink: {
|
||||||
|
textDecoration: "inherit",
|
||||||
|
color: "inherit",
|
||||||
|
"&:link": {
|
||||||
|
textDecoration: "inherit",
|
||||||
|
color: "inherit",
|
||||||
|
cursor: "auto",
|
||||||
|
},
|
||||||
|
"&:visited": {
|
||||||
|
textDecoration: "inherit",
|
||||||
|
color: "inherit",
|
||||||
|
cursor: "auto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tableSize: { height: 700, width: "100%" },
|
||||||
|
whiteBackground: { backgroundColor: "White" },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useStyles;
|
export default useStyles;
|
||||||
|
|||||||
@@ -1,19 +1,6 @@
|
|||||||
const grafanaAPI = {
|
const grafanaAPI = {
|
||||||
getCarsCount: async () => fetch(`${API_ENDPOINT}/?query=SELECT%20countDistinct(vin)%20as%20count%0AFROM%20default.vehicle_data%20FORMAT%20JSON`, {
|
getCarsCount: async () => 500,
|
||||||
method: "GET",
|
getSignalsCount: async () => 1234567890,
|
||||||
headers: Object.assign({ "Content-Type": "application/json" }),
|
|
||||||
})
|
|
||||||
.then(fetchRespHandler)
|
|
||||||
.then(result => result.data[0].count)
|
|
||||||
.catch(error => console.log(error)),
|
|
||||||
|
|
||||||
getSignalsCount: async () => fetch(`${API_ENDPOINT}/?query=SELECT%20count()%20as%20count%0AFROM%20default.vehicle_signal%20FORMAT%20JSON`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: Object.assign({ "Content-Type": "application/json" }),
|
|
||||||
})
|
|
||||||
.then(fetchRespHandler)
|
|
||||||
.then(result => result.data[0].count)
|
|
||||||
.catch(error => console.log(error)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default vehiclesAPI;
|
export default grafanaAPI;
|
||||||
|
|||||||
@@ -1,6 +1,23 @@
|
|||||||
|
import { fetchRespHandler } from "../utils/http"
|
||||||
|
|
||||||
|
const API_ENDPOINT = "https://grafana.fiskerdps.com/api/datasources/proxy/2"
|
||||||
|
|
||||||
const grafanaAPI = {
|
const grafanaAPI = {
|
||||||
getCarsCount: async () => 500,
|
getCarsCount: async () => fetch(`${API_ENDPOINT}/?query=SELECT%20countDistinct(vin)%20as%20count%0AFROM%20default.vehicle_data%20FORMAT%20JSON`, {
|
||||||
getSignalsCount: async () => 1234567890,
|
method: "GET",
|
||||||
|
headers: Object.assign({ "Content-Type": "application/json" }),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler)
|
||||||
|
.then(result => result.data[0].count)
|
||||||
|
.catch(error => console.log(error)),
|
||||||
|
|
||||||
|
getSignalsCount: async () => fetch(`${API_ENDPOINT}/?query=SELECT%20count()%20as%20count%0AFROM%20default.vehicle_signal%20FORMAT%20JSON`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: Object.assign({ "Content-Type": "application/json" }),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler)
|
||||||
|
.then(result => result.data[0].count)
|
||||||
|
.catch(error => console.log(error)),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default grafanaAPI;
|
export default grafanaAPI;
|
||||||
|
|||||||
23
src/utils/statusMessage.js
Normal file
23
src/utils/statusMessage.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const DOWNLOAD_STATUSES = ["download_start", "downloading", "download_complete", "download_error", "package_download_complete"];
|
||||||
|
const INSTALL_STATUSES = ["install_start", "installing", "install_complete", "install_error", "pacakge_install_complete"];
|
||||||
|
|
||||||
|
export const isDownloadStatusMessage = (status) => {
|
||||||
|
return DOWNLOAD_STATUSES.indexOf(status.msg) > -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isInstallStatusMessage = (status) => {
|
||||||
|
return INSTALL_STATUSES.indexOf(status.msg) > -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateStatusMessage = (status) => {
|
||||||
|
if (isDownloadStatusMessage(status)) {
|
||||||
|
if (!(status.package_current > -1 && status.package_total > -1)) return false;
|
||||||
|
if (status.package_current > status.package_total) return false;
|
||||||
|
return true;
|
||||||
|
} else if (isInstallStatusMessage(status)) {
|
||||||
|
if (!(status.installed > -1 && status.total_files > -1)) return false;
|
||||||
|
if (status.installed > status.total_files) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
48
src/utils/statusMessage.test.js
Normal file
48
src/utils/statusMessage.test.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { isDownloadStatusMessage, isInstallStatusMessage, validateStatusMessage} from "./statusMessage";
|
||||||
|
|
||||||
|
describe("validatons", () => {
|
||||||
|
it("isDownloadStatusMessage", () => {
|
||||||
|
expect(isDownloadStatusMessage({ msg: "download_start"})).toEqual(true);
|
||||||
|
expect(isDownloadStatusMessage({ msg: "downloading"})).toEqual(true);
|
||||||
|
expect(isDownloadStatusMessage({ msg: "download_complete"})).toEqual(true);
|
||||||
|
expect(isDownloadStatusMessage({ msg: "download_error"})).toEqual(true);
|
||||||
|
expect(isDownloadStatusMessage({ msg: "package_download_complete"})).toEqual(true);
|
||||||
|
|
||||||
|
expect(isDownloadStatusMessage({ msg: "installing"})).toEqual(false);
|
||||||
|
expect(isDownloadStatusMessage({ msg: "install_start"})).toEqual(false);
|
||||||
|
expect(isDownloadStatusMessage({ msg: "install_complete"})).toEqual(false);
|
||||||
|
expect(isDownloadStatusMessage({ msg: "install_error"})).toEqual(false);
|
||||||
|
expect(isDownloadStatusMessage({ msg: "pacakge_install_complete"})).toEqual(false);
|
||||||
|
});
|
||||||
|
it("isInstallStatusMessage", () => {
|
||||||
|
expect(isInstallStatusMessage({ msg: "download_start"})).toEqual(false);
|
||||||
|
expect(isInstallStatusMessage({ msg: "downloading"})).toEqual(false);
|
||||||
|
expect(isInstallStatusMessage({ msg: "download_complete"})).toEqual(false);
|
||||||
|
expect(isInstallStatusMessage({ msg: "download_error"})).toEqual(false);
|
||||||
|
expect(isInstallStatusMessage({ msg: "package_download_complete"})).toEqual(false);
|
||||||
|
|
||||||
|
expect(isInstallStatusMessage({ msg: "installing"})).toEqual(true);
|
||||||
|
expect(isInstallStatusMessage({ msg: "install_start"})).toEqual(true);
|
||||||
|
expect(isInstallStatusMessage({ msg: "install_complete"})).toEqual(true);
|
||||||
|
expect(isInstallStatusMessage({ msg: "install_error"})).toEqual(true);
|
||||||
|
expect(isInstallStatusMessage({ msg: "pacakge_install_complete"})).toEqual(true);
|
||||||
|
});
|
||||||
|
it("validateStatusMessage", () => {
|
||||||
|
expect(validateStatusMessage({ msg: "download_start", package_current: 0, package_total: 0})).toEqual(true);
|
||||||
|
expect(validateStatusMessage({ msg: "downloading", package_current: 0, package_total: 0})).toEqual(true);
|
||||||
|
expect(validateStatusMessage({ msg: "download_complete", package_current: 0, package_total: 0})).toEqual(true);
|
||||||
|
expect(validateStatusMessage({ msg: "download_error", package_current: 0, package_total: 0})).toEqual(true);
|
||||||
|
expect(validateStatusMessage({ msg: "package_download_complete", package_current: 0, package_total: 0})).toEqual(true);
|
||||||
|
|
||||||
|
expect(validateStatusMessage({ msg: "installing", installed: 0, total_files: 0 })).toEqual(true);
|
||||||
|
expect(validateStatusMessage({ msg: "install_start", installed: 0, total_files: 0 })).toEqual(true);
|
||||||
|
expect(validateStatusMessage({ msg: "install_complete", installed: 0, total_files: 0 })).toEqual(true);
|
||||||
|
expect(validateStatusMessage({ msg: "install_error", installed: 0, total_files: 0 })).toEqual(true);
|
||||||
|
expect(validateStatusMessage({ msg: "pacakge_install_complete", installed: 0, total_files: 0 })).toEqual(true);
|
||||||
|
|
||||||
|
expect(validateStatusMessage({ msg: "downloading", package_current: -1, package_total: -1})).toEqual(false);
|
||||||
|
expect(validateStatusMessage({ msg: "installing", installed: -1, total_files: -1})).toEqual(false);
|
||||||
|
expect(validateStatusMessage({ msg: "download_start", package_current: 100, package_total: 50})).toEqual(false);
|
||||||
|
expect(validateStatusMessage({ msg: "install_start", installed: 10, total_files: 9})).toEqual(false);
|
||||||
|
});
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user